You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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_SERVER
  • MAILGUN_SMTP_PORT
  • MAILGUN_SMTP_LOGIN
  • MAILGUN_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为例:

  1. 安装依赖:pip install django-background-tasks
  2. 在settings.py的INSTALLED_APPS中添加'background_task'
  3. 迁移数据库:python manage.py migrate
  4. 将邮件发送函数改为任务:
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
    )
  1. 在view中调用任务:
send_verification_email("Email conf", verificationLink, noReply, [email])
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 06:06:23