指南 · 邮件退信
产品团队应如何安全地诊断和处理邮件退信?
请把一封退信视为与某个收件人和某次发送尝试相关联的投递证据,而不是一个笼统的“失败”标记。保留原始邮件标识符、信封发件人、收件人、SMTP 状态码或增强状态码、诊断信息、报告服务器和事件时间。把即时的 SMTP 拒收与之后的投递状态通知区分开来。只对暂时性的 4.x 结果进行重试,并设置有上限的退避和队列存留时间限制;在确认出现永久性的 5.x 失败后,停止针对该收件人的重试并将其加入抑制列表。对服务商事件进行身份验证和去重,避免产生反向散射(backscatter),并把服务商受理、目标服务器接收、之后的投递失败和收件箱送达作为彼此独立的状态。
确定失败是在哪里被观察到的
产品可能在实时 SMTP 会话中、通过之后的投递状态通知,或通过经过身份验证的服务商事件得知投递失败。这些观察结果对应的证据各不相同。即时的 RCPT TO 拒绝发生在邮件数据被接收之前,只针对该收件人。DATA 阶段的拒绝可能针对整个已提交的会话。之后收到的 DSN 则表示某个系统曾接受了投递责任,但随后未能投递或转发该邮件。请记录阶段、服务器、收件人范围、发送尝试、时间戳、SMTP 回复、增强状态码、诊断信息以及原始的关联标识符。不要把所有情况都归为“已退信”。只在运营和政策需要的期限内保留原始的服务商负载或标准格式的 DSN,并限制访问。客服截图或人工转述不足以作为自动重试、抑制或面向客户的状态的依据。
区分暂时性结果与永久性结果
SMTP 使用 4yz 回复表示暂时性否定完成,使用 5yz 回复表示永久性否定完成。增强状态码在此基础上增加一个类别位:以 4 开头表示持续性的暂时失败,以 5 开头表示永久失败,其后是主题值和细节值。请同时保留基本状态码和增强状态码,因为仅凭文本描述因服务商而异,而且可能变化。暂时性结果可以作为延迟后重试同一逻辑邮件的理由,但不能作为立即循环重试或无限期留在队列中的理由。对于永久性的收件人失败,应停止针对该收件人和该次尝试的自动重放,直到地址或策略通过授权流程发生变化。不要仅凭服务商非正式的标签来判断是硬退信还是软退信。请根据确切的状态码、阶段、诊断信息、邮件类别、收件人以及服务商当前的文档来制定策略。未知或格式错误的响应应以“失败即关闭”的方式进入人工审查或死信处理,而不是默认重新发送。
以防御性方式解析投递状态通知
RFC 3464 定义了一种机器可读的投递状态通知格式,它以 multipart/report 承载,并包含 message/delivery-status 字段。有用的字段包括 Reporting-MTA、Final-Recipient、Action、Status、Remote-MTA、Diagnostic-Code,以及到达时间或最后尝试时间。即使 MIME 结构能够成功解析,也要把每个字段都视为不可信的输入。对邮件大小、邮件头数量、部件数量、嵌套层级、字符解码以及存储的诊断信息长度设置上限。切勿执行附件、自动访问诊断信息中的链接,或把收件人地址当作租户身份。请使用稳定的服务商邮件标识符、原始信封元数据或保护隐私的关联邮件头,将其与应用自有的发送尝试对应起来。DSN 可能包含原始邮件的部分内容和收件人数据,因此请限制日志记录和保留期限。如果对应关系不明确,请保留证据,但不要抑制无关的地址,也不要泄露其他租户的邮件历史。
为收件人级别的状态转换建模
一封邮件可以面向多个收件人,而且各收件人的结果可能不同。请按收件人和发送尝试存储状态,而不仅仅记录在邮件这一行上。服务商可能接受部分 RCPT 命令而拒绝其他命令,也可能之后报告一个收件人已投递而另一个收件人失败。请定义单调的状态转换,确保延迟到达的“已受理”或“已延迟”事件不会覆盖之后确认的永久失败、投诉或退订。保留事件账本,并通过明确的优先级规则推导当前显示的状态。区分已提交、服务商已受理、收件服务器已接收、暂时延迟、永久失败、已抑制、已投诉、已退订和未知。目标服务器接收了邮件,仍然无法说明邮件最终所在的文件夹,也无法说明有人阅读了它。让重试在同一个逻辑事件键下生成相互关联的发送尝试,使重复发送的风险和证据保持可见。不要把计划中的重试标记为新的客户操作。
在严格限制下重试暂时性失败
对于符合条件的暂时性结果,请使用带抖动的指数退避,并设置有限的尝试次数和最长队列存留时间。遵循服务商文档中的重试行为,不要在本身已经会重试的中继之上再叠加一层激进的应用重试循环。每次尝试都要保持相同的逻辑邮件身份,并执行抑制检查。当收件人被抑制、许可状态变化、事件过期、发件身份被撤销或之后收到永久性响应时,停止重试。按租户、目标域名、发件人和失败类别进行速率限制,避免某一个收件方的故障占满整个队列。如果存在 Retry-After 或文档中说明的延迟指引,请予以遵守,但绝不要把任意邮件内容当作重试指令。当队列存留时间上升、暂时性状态码反复出现、出现异常域名或尝试即将过期时发出告警。暂时性状态码可能掩盖持续存在的策略或信誉问题;有限的重试是为恢复争取时间,而不是忽视根本原因的许可。
抑制已确认的永久性收件人失败
在确认地址或邮箱出现永久性失败后,应在提交任何后续任务之前更新由产品维护的抑制记录。存储租户、规范化的收件人键、作用范围、来源事件、状态和诊断类别、生效时间以及证据引用,同时不要大范围暴露该地址。在发送时应用抑制,而不仅仅是在导入列表时。区分无效地址、不存在的域名、策略拒收、邮件内容拒收、身份验证失败、配额以及发件人信誉,因为它们的安全补救措施各不相同。无效收件人适合进行收件人范围的抑制;而发件人身份验证被拒,则应暂停该发件人配置,而不是抑制所有收件人。手动移出抑制列表必须经过严格授权,并附带原因和审计记录。重新确认或更正必须形成一个新的、经过验证的决定,而不是删除旧的证据。服务商的抑制列表很有用,但不能替代应用自身的许可与安全账本,尤其是在迁移服务商期间。
避免退信循环和反向散射
SMTP 对投递状态通知使用空的反向路径,这样即使通知本身投递失败,也不会再产生新的退信。请在中继中保持这一行为,不要对 DSN、自动回复或带有自动生成标志的邮件发送自动回复。RFC 3834 给出了关于自动邮件回复的建议,包括对循环和放大问题的考虑。在接收了一封可疑邮件之后,切勿向未经验证的可见 From 地址发送退信,因为伪造的发件人身份可能把您的系统变成反向散射的来源。尽可能在 SMTP 会话中直接拒绝无效收件人,而不是先接收、再通知一个伪造的地址。按发件人和会话限制自动回复的数量,并使用受控的回复身份。产品 Webhook 或内部错误事件通常比生成新的互联网邮件更安全。请在隔离的测试夹具中测试伪造的 From、空的信封发件人、重复的 DSN、Auto-Submitted 邮件头、邮件列表流量以及格式错误的报告。
应用服务商事件之前先验证其身份
如果服务商通过 Webhook 推送退信事件,请在解析业务字段之前,先按文档中的签名或身份验证机制对原始请求进行验证。强制检查时间戳的新鲜度,启用重放保护,限制请求体大小,并完成租户关联。在返回成功之前,先持久化或入队经过验证的事件,然后按稳定的服务商事件标识符,或一个不会合并不同收件人或发送尝试的保守组合键进行去重。将发生时间与处理时间分开存储,因为事件可能延迟到达且顺序错乱。如果事件的发件域名、账户、工作区、邮件标识符或收件人范围无法与预期的租户对应,请拒绝该事件。Webhook 密钥应与 SMTP 或 API 凭据分开轮换。监控签名失败、重复率、延迟、死信和未知事件类型。经过身份验证的 Webhook 只能证明它在所配置的密钥下来源可信;在关联成功之前,它并不能证明该事件已对应到正确的内部任务。
按状态码类别诊断,而不是靠猜测措辞
从增强状态码的主题开始:地址状态、邮箱状态、邮件系统状态、网络或路由状态、邮件投递协议状态、邮件内容或媒体状态,或者安全与策略状态。然后结合细节代码、完整的诊断信息以及收件方或服务商当前的文档进行分析。对于地址失败,核实收件人语法和域名 DNS;对于邮箱失败,核实邮箱是否存在以及配额证据;对于传输失败,核实 MX、路由、TLS 和网络证据;对于邮件失败,核实大小、MIME、编码和内容;对于安全失败,核实 SPF、DKIM、DMARC、凭据、发件人策略或信誉。每次受控复测只改变一个变量。不要通过更换 IP、域名或服务商来规避永久性的策略决定。保留原始响应;对于扩大了发件权限或削弱了身份验证、却没有解决所观察到的原因的配置变更,请予以回滚。
在不泄露收件人数据的前提下衡量退信健康度
按保护隐私的分组,跟踪首次尝试受理、暂时延迟、永久失败、重试后恢复、结果未知、投诉、抑制以及队列存留时间。有用的维度包括发信域名、按批准的聚合级别统计的目标域名、邮件类别、模板版本、服务商、状态码类别和时间。避免在日常分析中使用完整地址、邮件内容、诊断信息原文或原始邮件头。将无效收件人比率与策略、身份验证、内容、信誉以及暂时性基础设施失败区分开来;单一的退信率会掩盖可以采取行动的原因。以收件人发送尝试为分母,并把迟到的事件计入其原始分组。根据历史基线和业务风险设置告警,而不是使用一个通用的百分比。审计抑制的执行情况和手动覆盖操作。只保留运营、安全、法律义务和争议处理所需的证据,然后将其删除或汇总。退信率低并不能证明获得了许可、有用户互动或进入了收件箱。
SendHQ 的适用场景
SendHQ 文档说明支持投递和退信跟踪及抑制记录。有关其支持的行为,请参阅当前文档。
常见问题
什么是邮件退信?
退信是表明某个 SMTP 收件人或之后的投递尝试已失败或被延迟的证据,可能在 SMTP 会话中、通过 DSN 或通过服务商事件报告。
4xx 退信和 5xx 退信有什么区别?
4xx 回复是暂时性的,可以作为有限次数重试的理由。5xx 回复对该次尝试而言是永久性的,通常需要修正问题或将地址加入抑制列表。
每个退信地址都应该加入抑制列表吗?
不是。只抑制已确认的永久性收件人失败。发件人身份验证、内容、信誉、配额或暂时性基础设施故障,需要采取范围不同的补救措施。请针对所观察到的原因和收件人范围采取补救措施。
一封邮件会部分退信吗?
会。SMTP 可以接受部分收件人而拒绝其他收件人,之后的 DSN 也可能为每个收件人报告不同的结果。请存储收件人级别的状态。
应如何重试暂时性退信?
使用同一个持久化的逻辑任务,采用带抖动的指数退避,限制尝试次数和队列存留时间,并在每次尝试之前重新执行抑制检查。
SMTP 返回 250 后,还会出现退信吗?
不会。服务器可以先接受投递责任,之后再生成投递失败的证据。目标服务器接收邮件,也不能说明邮件进入了收件箱或有人阅读。
为什么退信要使用空的反向路径?
空的反向路径可以防止 DSN 投递失败时再生成新的 DSN,从而避免退信循环和放大。
SendHQ 会处理退信吗?
会。SendHQ 文档说明支持投递和退信跟踪及抑制记录。
参考来源
- RFC 3464:一种可扩展的投递状态通知邮件格式 — RFC Editor
- RFC 3463:增强型邮件系统状态码 — RFC Editor
- RFC 5321:简单邮件传输协议(SMTP) — RFC Editor
- RFC 3834:关于电子邮件自动回复的建议 — RFC Editor