Django 6.1 邮件配置大改:旧项目如何平稳升级?

Django 项目里的邮件配置,很多年都长得差不多:一个 EMAIL_BACKEND,再配上一组 EMAIL_HOST、EMAIL_PORT 和账号密码。项目只有一个发信通道时,这套配置够用;但当订单通知、管理员告警和营销邮件需要不同服务商时,代码往往开始到处传连接对象。
Django 6.1 RC1 引入了新的 MAILERS 配置,并计划在 Django 7.0 移除旧邮件配置。它不是简单地给设置项换名字,而是把"一个全局邮件后端"改成了"多个具名邮件通道"。本文用一个隔离实验验证迁移路径,并给出老项目可以直接执行的升级清单。
当前 Django 6.1 仍是候选版本,不建议直接替换生产环境。本文适合提前改造、运行测试和排查第三方包兼容性。
旧配置为什么开始吃力
典型的旧项目通常这样配置:
python
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"]
它的优点是直观,但全站默认只有一套连接参数。如果事务邮件使用国内服务商、营销邮件使用海外服务商、管理员告警还要投递到本机中继,就需要手动创建不同连接,或者依赖第三方封装。
Django 6.1 把配置方式改成了与 DATABASES、CACHES、STORAGES 类似的字典结构:
python
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 选择通道:
python
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 次 |
| 事务邮件主题 | 订单创建成功 |
| 营销邮件主题 | 八月技术通讯 |
| 收件地址 | 两封都正确 |
这证明新配置可以在同一项目中按业务选择后端,而且测试时仍能使用内存后端检查邮件主题、收件人和数量。

老项目最稳妥的迁移顺序
第一步:先盘点,不要边删边改
搜索项目中的这些内容:
text
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,很容易把发送失败吞掉。更可靠的做法是让后端抛出异常,在业务层决定重试、记录失败任务还是降级通知。
python
try:
send_order_email(order, using="transactional")
except Exception:
logger.exception("订单邮件发送失败", extra={"order_id": order.id})
enqueue_email_retry(order.id)
日志只记录业务标识和脱敏地址,不要输出 SMTP 密码、完整凭据或邮件正文。
第五步:在接近生产的环境打开弃用警告
先运行完整测试,再在预发布环境检查第三方包是否仍读取旧设置。确认所有通道都能连接、超时、重试和报警后,再删除旧配置。

三个容易踩的坑
1. 把配置改名当成迁移完成
真正的迁移还包括 get_connection()、连接参数、自定义后端和第三方包。只修改 settings.py,并不能证明运行路径已经切换。
2. 在代码里直接写 SMTP 密码
新结构把所有参数集中到了 OPTIONS,但这不代表可以提交凭据。用户名、密码、接口密钥仍应来自环境变量或密钥服务。
3. 测试通过就认为生产邮件一定正常
内存后端只能证明调用、路由和内容组装正确,不能证明 DNS、TLS、端口、服务商限流、发件域名验证和退信处理正确。上线前仍要做受控的真实投递测试。
结论与最后的迁移清单
- 所有旧邮件设置和连接调用已经盘点;
-
MAILERS至少包含default; - 不同业务使用明确别名;
- SMTP 凭据没有进入代码和日志;
- 自定义后端兼容新的
OPTIONS; - 失败策略不再依赖静默吞错;
- 单元测试、预发布投递和监控都已验证;
- 第三方包不再依赖即将移除的接口。
Django 6.1 的邮件改造,真正的价值不是配置变得更"新",而是让事务邮件、营销邮件和系统告警拥有清晰的路由边界。老项目最稳妥的策略也不是一次性删除旧代码,而是先增加新通道、逐条切换调用、扩大验证范围,最后再清理兼容层。