Django 项目的邮件配置长期围绕 EMAIL_BACKEND、EMAIL_HOST、EMAIL_PORT 这一套展开。单一发信通道时它足够直接,但一旦项目里同时存在订单通知、管理员告警、营销邮件等不同场景,旧方案就会把连接细节扩散到业务代码里。
Django 6.1 RC1 带来的 MAILERS,本质上是把“全站只有一个默认邮件后端”改成“按业务选择具名邮件通道”。下面按升级动机、实验结果和落地顺序来拆解,方便你判断旧项目该怎么迁、先迁到哪一步最稳。
为什么旧邮件配置开始不够用了
很多老项目的配置大致类似下面这样:

EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.example.com"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.environ["EMAIL_ACCOUNT"]
EMAIL_HOST_PASSWORD = os.environ["EMAIL_PASSWORD"]
这套方式的优点是简单,问题也同样明显:全站默认只有一套连接参数。只要不同业务需要不同服务商、不同投递策略,代码里就容易出现手动创建连接对象、传递 connection 参数,甚至额外引入第三方封装的情况。
Django 6.1 把这部分能力改造成了与 DATABASES、CACHES、STORAGES 相似的字典结构:
MAILERS = {
"default": {
"BACKEND": "django.core.mail.backends.smtp.EmailBackend",
"OPTIONS": {
"host": os.environ["DEFAULT_SMTP_HOST"],
"port": 587,
"use_tls": True,
"username": os.environ["DEFAULT_SMTP_USER"],
"password": os.environ["DEFAULT_SMTP_PASSWORD"],
},
},
"transactional": {
"BACKEND": "django.core.mail.backends.smtp.EmailBackend",
"OPTIONS": {
"host": os.environ["ORDER_SMTP_HOST"],
"port": 465,
"use_ssl": True,
"username": os.environ["ORDER_SMTP_USER"],
"password": os.environ["ORDER_SMTP_PASSWORD"],
},
},
}
调用时也不再强调先拼接连接对象,而是直接通过 using 选择通道:
from django.core.mail import send_mail
send_mail(
"订单创建成功",
"订单已进入处理流程。",
"service@example.com",
["reader@example.com"],
using="transactional",
)
这意味着邮件路由开始从“代码里传连接”转向“配置层声明通道,业务层只选别名”。对于历史包袱较重的项目,这个变化比改设置名更关键。
隔离实验验证了什么
为了确认新配置是否真的适合老项目迁移,文中的实验环境做了刻意简化:
- Windows 11;
- Python 3.12.1;
- Django 6.1 RC1;
- 本地内存邮件后端,不连接真实 SMTP,不产生外部邮件。
实验里配置了三个别名:default、transactional 和 marketing,再分别通过后两个通道发送订单通知和技术通讯。结果如下:
| 检查项 | 结果 |
|---|---|
| 已识别邮件通道 | 3 个 |
| 隔离发信次数 | 2 次 |
| 事务邮件主题 | 订单创建成功 |
| 营销邮件主题 | 八月技术通讯 |
| 收件地址 | 两封都正确 |
这个结果至少说明两件事。第一,同一项目里可以按业务选择不同邮件后端;第二,在测试环境中仍然可以继续使用内存后端,检查邮件数量、主题和收件地址,不必一上来就连真实 SMTP。
不过也要注意,内存后端只能验证调用路径和内容组装正确,不能证明真实网络、TLS、DNS、限流和发件域名验证一定没问题。它适合做迁移初验,不适合替代上线前的真实投递测试。
老项目更稳妥的迁移顺序
第一步:先盘点依赖点,不要边删边改
开始之前,先把项目里与旧邮件配置相关的调用全部找出来:

EMAIL_BACKEND
EMAIL_HOST
get_connection(
connection=
fail_silently=
auth_user=
auth_password=
检查范围不要只停留在自己的业务代码。邮件模板库、账号系统、后台任务、自定义邮件后端都可能直接依赖旧接口。原文特别提到,测试环境经常会自动替换邮件后端,所以只跑单元测试,未必能看到生产配置触发的弃用警告。
第二步:先加上 MAILERS,暂时保留旧配置
迁移初期可以先定义 MAILERS,让新增代码优先使用别名;旧配置暂时保留,便于按模块逐步切换。这样做的价值在于把变量拆开:Django 版本升级是一件事,服务商切换、域名调整、账号替换是另外几件事,不要挤在同一个提交里同时处理。
如果一次性同时改了服务商、凭据、域名和框架版本,回滚或定位问题都会变得很困难。
第三步:把连接对象调用改成 using
旧代码里常见的模式,是先调用 get_connection(),再把连接传给 send_mail() 或 EmailMessage。在 Django 6.1 里,更推荐使用 mail.mailers["别名"],或者直接在发送函数里传 using="别名"。
这一步的重点不是机械替换,而是清理那些把连接细节暴露给业务层的封装。如果某个工具函数允许外部传 connection、auth_user 或 auth_password,就说明配置边界仍然不清晰,应该尽量把这些参数收回到配置层。
第四步:顺手把失败策略理清楚
fail_silently 也在弃用路径上。历史项目里常见的 fail_silently=True,最大的问题不是“写法旧”,而是它会把真实发送失败静默吞掉,导致业务层看起来一切正常,实际上通知已经丢失。
更稳妥的方式,是让后端正常抛出异常,再由业务层决定记录日志、触发重试或切换降级路径:
try:
send_order_email(order, using="transactional")
except Exception:
logger.exception("订单邮件发送失败", extra={"order_id": order.id})
enqueue_email_retry(order.id)
日志里只保留业务标识和脱敏地址,不要输出 SMTP 密码、完整凭据或邮件正文。
第五步:在接近生产的环境验证弃用与连通性
完成代码切换后,先跑完整测试,再去预发布环境确认第三方包是否仍在读取旧设置。最终需要验证的不只是“能发出去”,还包括通道连接、超时、重试、告警,以及不同别名是否真的走到了预期服务商。
等这些都通过之后,再删除旧配置会更稳。
三个最容易踩的坑
1. 以为改了配置名就算迁移完成
真正需要迁移的,不止 settings.py。get_connection()、连接参数、自定义后端、第三方依赖都可能仍停留在旧路径上。只改配置名,不能证明运行时已经切到新机制。

2. 把 SMTP 凭据直接放进代码
MAILERS 把参数集中到了 OPTIONS,但这不代表用户名、密码、接口密钥就可以直接写死。凭据仍然应该来自环境变量或密钥服务,这一点在新旧配置里没有变化。
3. 内存后端通过,不代表生产投递没问题
内存后端只能证明路由与内容正确,不能覆盖 DNS、TLS、端口、服务商限流、发件域名验证和退信处理。上线前仍要做受控的真实投递测试,否则最关键的问题往往会留到生产环境才暴露。
最后给老项目一份迁移清单
- 所有旧邮件设置和连接调用已经盘点;
-
MAILERS至少包含default; - 不同业务使用明确别名;
- SMTP 凭据没有进入代码和日志;
- 自定义后端兼容新的
OPTIONS; - 失败策略不再依赖静默吞错;
- 单元测试、预发布投递和监控都已验证;
- 第三方包不再依赖即将移除的接口。
Django 6.1 这次邮件配置调整,真正带来的不是“写法更新”,而是更清晰的业务路由边界。对旧项目来说,最稳的升级方法也不是一次删干净旧配置,而是先引入 MAILERS、再逐步把调用迁到别名通道,最后用接近生产的环境把连通性和失败处理补齐。
参考资料
- Django 6.1 发布说明
- Django 邮件配置迁移指南
- Django 发送邮件文档













