旧收件域名已停用:大附件邮件堆满队列

邮件队列涨到数万封、/var 分区告警,根因是报表任务仍向已停用的旧域名群发带附件邮件,MX 解析失败后在队列里重试五天。清理队列、更新收件域并加监控后恢复。

问题背景

公司去年收购了一家子公司,今年上半年把子公司的邮箱整体迁移到集团的新域名,旧域名保留转发一段时间后,上个月正式下掉了 MX 记录。迁移项目由 IT 基础架构组负责,邮箱、通讯录、日历都迁完了,验收时重点看的是“员工能不能正常收发邮件”。

总部 ERP 有一个运行了两年的日报任务:每晚 8 点给子公司三千名员工各发一份 PDF 日报,单个附件大约 3MB。这个任务的收件人列表是实施时从旧通讯录导出的,全部是 @oldco.example 地址。邮箱迁移时没有人想到去检查应用侧的发件配置,下 MX 之前也没有做邮件流量审计。

故障现象

  • 邮箱迁移后的前两周,子公司员工陆续反馈“收不到日报”;ERP 日志显示发送成功,SMTP 返回 250,应用侧没有任何报错。
  • 一个月后,监控告警:邮件服务器 /var 分区使用率冲到 96%,Postfix 队列积压超过一万五千封。
  • 总部其他外发邮件开始延迟,日志里偶尔出现 queue file write error,部分新邮件被直接拒绝。
  • qshape deferred 显示队列里 90% 以上的邮件指向 oldco.example,正是那个已经停用的旧域名。
  • 队列里的邮件不是退信,而是完整的日报:每封都带着 3MB 的 PDF 附件。
  • 磁盘使用率在每天晚八点后明显抬升,白天几乎不下降。

排查过程

第一步看队列整体。postqueue -p 的尾部汇总显示队列里有一万多封邮件、占用十几 GB;qshape deferred 按域名分布排序,oldco.example 遥遥领先,其他域名只有零星几封。问题高度集中,不是全网邮件故障。

第二步抽样看内容。从 postqueue -p 里取一个队列 ID,用 postcat -q <QID> 打印,发现发件人是 [email protected],收件人是 @oldco.example 的员工地址,主题是日报,大小约 3MB。队列里堆积的不是退信,而是根本没有投递出去的原邮件。

第三步查投递日志。maillog 里对应的记录是 Host or domain name not found. Name service error for name=oldco.example type=MX: Host not found, try again,状态 deferred。这解释了为什么应用看到 250:SMTP 的 250 只表示“服务器已接收并负责投递”,不代表对方已经收到。邮件进入队列后,DNS 解析失败,Postfix 按临时故障处理,开始退避重试。

第四步确认域名状态。dig MX oldco.example 返回 NXDOMAIN,dig MX newco.example 正常。旧域名的 MX 确实已经删除,新域名的邮件系统工作正常,说明迁移本身没问题,问题在于还有应用往旧地址发信。

第五步算容量。每晚三千封、每封 3MB,一天就是 9GB;Postfix 默认 maximal_queue_lifetime 是 5 天,重试期间邮件一直留在队列里,最坏情况会累积到 40GB 以上。服务器的 /var 分区总共 60GB,原本使用率就有七成,几天之内被队列填满是必然结果。

第六步查配置。ERP 的收件人列表仍然是旧域名,没有做有效性校验;Postfix 侧没有队列深度监控,也没有在域名下线前做过发件人审计。整个链路里没有任何一个环节会发现“这些邮件永远送不到”。

解决方案

  1. 先止血:暂停 ERP 的日报任务,避免继续往队列里灌邮件;用 postqueue -p 配合 grep oldco.example 找出目标域名的队列 ID,分批 postsuper -d <QID> 删除。不要直接 postsuper -d ALL,队列里可能还有正常的延迟邮件。
  2. 修正收件人:从新域名通讯录重新导出收件人列表,更新 ERP 配置;发送前做地址格式和域名有效性校验,失效地址直接剔除并记录,而不是交给 MTA 慢慢重试。
  3. 快速失败:如果短期内还有历史数据要发往旧域名,可以在 transport_maps 里给 oldco.example 配 error:5.1.1,让 Postfix 在接收阶段就拒绝,避免再次进入队列。治本仍然是改配置。
  4. 压缩队列生命周期:把 maximal_queue_lifetime 从默认 5 天调整为 2 天,bounce_queue_lifetime 调整为 1 天;对确定不可达的域名,重试五天没有意义,只会放大磁盘压力。
  5. 降低单封体积:日报 PDF 改为上传到文件服务、邮件里只发下载链接,或者按人按需生成。3MB 的附件乘以三千人,放大的不只是带宽,还有队列和备份的磁盘占用。
  6. 补监控:采集队列总数、qshape deferred 的 Top 域名、最老 deferred 邮件年龄、/var 使用率;队列超过阈值或最老邮件超过 24 小时就告警。这样在磁盘被填满之前就能发现异常。

根因分析

直接根因是 ERP 仍向已停用的旧域名群发带大附件的日报,旧域名 MX 删除后邮件无法投递,Postfix 按临时故障在队列里重试五天,三千封 × 3MB 的日增量迅速把 /var 分区填满。

更深一层,是把域名迁移当成了“邮箱搬家”,而没有当成“应用依赖迁移”。邮箱、通讯录迁完了,但所有引用旧域名的系统配置、收件人列表、发件地址都没有被盘点;下 MX 之前也没有审计还有多少流量指向旧域名。迁移项目以“员工能收信”为验收标准,恰好漏掉了“系统还能不能发信”。

还有一层是对 SMTP 的误解。应用看到 250 就认为发送成功,实际上那只是“已入队”;真正的投递结果要等队列重试结束才知道,而应用既不处理退信,也没人看队列。邮件系统在替应用默默重试了五天,直到磁盘告警才把问题暴露出来。

预防措施

  • 域名下线或邮箱迁移必须走“应用依赖清单”:全量扫描配置、数据库、脚本里的旧域名,逐项确认发件人和收件人,迁移完成后再保留至少一个月的 MX 过渡期。
  • 应用发送邮件前做收件人有效性校验,失效地址进入抑制列表,不再重复投递;批量任务要有发送量上限和失败率熔断。
  • 邮件队列纳入基础监控:队列长度、Top 目标域名、最老邮件年龄、队列目录磁盘使用率,四项都要有阈值告警。
  • 大附件改为链接下载,控制单封邮件体积;批量报表按需发送,避免“三千人 × 3MB”这种线性放大。
  • 迁移验收增加“金丝雀收件人”:在新旧域名各放一个受控邮箱,确认收发链路都正常,而不是只看迁移当天的一封测试邮件。

总结

SMTP 的 250 不是“对方收到了”,而是“我替你收下了”。邮件队列是一个磁盘支撑的等待室,正常情况下它安静地重试几分钟;一旦目标域名永远不可达,它就会安静地吃掉几天、几十 GB 的磁盘。这次故障里没有攻击、没有硬件损坏,只有一个被遗忘的旧域名和一串没人核对的收件人地址。域名迁移的验收标准,不该只是“员工能收信”,还应该包括“所有系统还能不能把信发出去”。

延伸阅读

使用 Hugo 构建
主题 Stack 由 Jimmy 设计