在Heroku的Django应用中使用Mailgun发邮件遇Errno 110超时错误
问题:Heroku部署Django应用使用Mailgun发邮件出现连接超时(Errno 110)
本地环境下Mailgun邮件发送功能正常,但部署到Heroku后,提交表单约一分钟会触发TimeoutError: [Errno 110] Connection timed out错误,页面虽能正常返回"pass",但邮件发送线程报错。
当前配置与代码
settings.py配置
EMAIL_HOST = "smtp.mailgun.org" MAIL_PORT = 587 EMAIL_HOST_USER = os.environ["MAILGUN_SMTP_LOGIN"] EMAIL_HOST_PASSWORD = os.environ["MAILGUN_SMTP_PASSWORD"] EMAIL_USE_TLS = True EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
views.py相关代码
import re import os from django.http import HttpResponse from django.contrib.auth.hashers import make_password import secrets from .models import User from .models import EmailConfirmation from django.core.mail import send_mail from threading import Thread noReply = re.sub(".*@", "noreply@", os.environ["MAILGUN_SMTP_LOGIN"]) def threadingSendMail(subject, message, from_email, recipientList): send_mail( subject=subject, message=message, from_email=from_email, recipient_list=recipientList ) def wait(request): if request.method == "POST": password = request.POST["password"] email = request.POST["email"] if not re.match(r"[^@]+@[^@]+\.[^@]+", email): return HttpResponse("Email not valid") else: if User.objects.filter(email=email).exists(): return HttpResponse("Email already exists") else: theUser = User.objects.create(email=email, password=make_password(password), confirmed=False) hashKey = secrets.token_hex(16) EmailConfirmation.objects.create(email=theUser,emailHash=hashKey) verificationLink = request.get_host() + "/" + str(hashKey) Thread(target=threadingSendMail, args=("Email conf", verificationLink, noReply, [email],)).start() return HttpResponse("pass") else: return HttpResponse("hello")
Heroku错误日志
2022-08-27T10:55:15.473726+00:00 app[web.1]: Exception in thread Thread-1 (threadingSendMail): 2022-08-27T10:55:15.473737+00:00 app[web.1]: Traceback (most recent call last): 2022-08-27T10:55:15.473738+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.10/threading.py", line 1016, in _bootstrap_inner 2022-08-27T10:55:15.473739+00:00 app[web.1]: self.run() 2022-08-27T10:55:15.473739+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.10/threading.py", line 953, in run 2022-08-27T10:55:15.473810+00:00 app[web.1]: self._target(*self._args, **self._kwargs) 2022-08-27T10:55:15.473820+00:00 app[web.1]: File "/app/djangocode/stocks/views.py", line 21, in threadingSendMail 2022-08-27T10:55:15.473912+00:00 app[web.1]: send_mail( 2022-08-27T10:55:15.473921+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.10/site-packages/django/core/mail/__init__.py", line 87, in send_mail 2022-08-27T10:55:15.474010+00:00 app[web.1]: return mail.send() 2022-08-27T10:55:15.474018+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.10/site-packages/django/core/mail/message.py", line 298, in send 2022-08-27T10:55:15.474128+00:00 app[web.1]: return self.get_connection(fail_silently).send_messages([self]) 2022-08-27T10:55:15.474146+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.10/site-packages/django/core/mail/backends/smtp.py", line 124, in send_messages 2022-08-27T10:55:15.474235+00:00 app[web.1]: new_conn_created = self.open() 2022-08-27T10:55:15.474243+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.10/site-packages/django/core/mail/backends/smtp.py", line 80, in open 2022-08-27T10:55:15.474326+00:00 app[web.1]: self.connection = self.connection_class( 2022-08-27T10:55:15.474337+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.10/smtplib.py", line 255, in __init__ 2022-08-27T10:55:15.474458+00:00 app[web.1]: (code, msg) = self.connect(host, port) 2022-08-27T10:55:15.474467+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.10/smtplib.py", line 341, in connect 2022-08-27T10:55:15.474589+00:00 app[web.1]: self.sock = self._get_socket(host, port, self.timeout) 2022-08-27T10:55:15.474590+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.10/smtplib.py", line 312, in _get_socket 2022-08-27T10:55:15.474707+00:00 app[web.1]: return socket.create_connection((host, port), timeout, 2022-08-27T10:55:15.474716+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.10/socket.py", line 845, in create_connection 2022-08-27T10:55:15.474920+00:00 app[web.1]: raise err 2022-08-27T10:55:15.474928+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.10/socket.py", line 833, in create_connection 2022-08-27T10:55:15.475117+00:00 app[web.1]: sock.connect(sa) 2022-08-27T10:55:15.475157+00:00 app[web.1]: TimeoutError: [Errno 110] Connection timed out
解决方案
1. 修复Django配置变量名错误
这是最可能导致超时的核心原因:你的settings.py中写的是MAIL_PORT = 587,但Django的SMTP后端读取的配置变量是**EMAIL_PORT**,而非MAIL_PORT。
Heroku默认封锁25端口(Django SMTP后端的默认端口),你配置的587端口因变量名错误未被读取,程序尝试用25端口连接Mailgun,最终超时。
修改settings.py:
EMAIL_HOST = "smtp.mailgun.org" EMAIL_PORT = 587 # 将MAIL_PORT改为EMAIL_PORT EMAIL_HOST_USER = os.environ["MAILGUN_SMTP_LOGIN"] EMAIL_HOST_PASSWORD = os.environ["MAILGUN_SMTP_PASSWORD"] EMAIL_USE_TLS = True EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
2. 使用Heroku-Mailgun集成的环境变量
若通过Heroku的Mailgun插件添加服务,Heroku会自动设置以下环境变量,直接使用可避免手动配置出错:
MAILGUN_SMTP_SERVERMAILGUN_SMTP_PORTMAILGUN_SMTP_LOGINMAILGUN_SMTP_PASSWORD
修改settings.py为:
EMAIL_HOST = os.environ.get("MAILGUN_SMTP_SERVER", "smtp.mailgun.org") EMAIL_PORT = int(os.environ.get("MAILGUN_SMTP_PORT", 587)) EMAIL_HOST_USER = os.environ["MAILGUN_SMTP_LOGIN"] EMAIL_HOST_PASSWORD = os.environ["MAILGUN_SMTP_PASSWORD"] EMAIL_USE_TLS = True EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
3. 替换原生线程为Heroku兼容的后台任务方案
Heroku的Web Dyno在请求结束后可能回收未完成的线程,导致邮件发送中断或超时。不建议用Python原生线程处理后台任务,推荐:
- django-background-tasks:轻量级后台任务库,适合简单场景
- Celery + Redis/RabbitMQ:适合复杂异步任务场景
以django-background-tasks为例:
- 安装依赖:
pip install django-background-tasks - 在settings.py的
INSTALLED_APPS中添加'background_task' - 迁移数据库:
python manage.py migrate - 将邮件发送函数改为任务:
from background_task import background @background(schedule=0) def send_verification_email(subject, message, from_email, recipientList): send_mail( subject=subject, message=message, from_email=from_email, recipient_list=recipientList )
- 在view中调用任务:
send_verification_email("Email conf", verificationLink, noReply, [email])
- 在Heroku启动任务进程:添加
worker到Procfile:worker: python manage.py process_tasks,然后执行heroku ps:scale worker=1
4. 增加SMTP超时配置
在settings.py中添加超时设置,避免无限等待连接:
EMAIL_TIMEOUT = 10 # 单位:秒
5. 验证Mailgun域名与权限
- 确认Mailgun域名已完成DNS验证
- 检查Mailgun的IP白名单,确保Heroku出站IP在允许范围内(可通过
heroku info查看Heroku出站IP)
内容的提问来源于stack exchange,提问作者Shabbs15
相关产品推荐
相关产品推荐

