月饮沙/AI

DINGDONG / 开发记录

邮件超时,不一定意味着没有投递

当正文可能已经被服务器接收,客户端却没等到最终响应,直接重发会把网络异常变成重复通知。

技术复盘 · 投递状态与异常处理 · 内容更新

01背景与现象

告警邮件回复遇到 SMTP 超时。若把所有超时记为失败,来源轮询器可能继续重发;但有些超时发生在正文发送之后,服务器可能已经接受邮件。

本文从实践记录中提炼投递状态设计,省略具体环境、事件时间与运行数据。下面的阶段顺序是模拟说明,不包含真实邮箱或邮件内容。

连接 → 协商与认证 → 发件人/收件人 → DATA
→ 发送正文 → 等待最终响应
                  ↑ 此处断连:结果需要核查

02排查证据

排查将网络失败发生的位置与服务器明确返回的状态分开记录。

  • 正文发送之前的网络失败,与正文发送中或等待最终响应时的断连,含义不同。
  • 服务器明确拒绝时,可以依据响应判断失败;最终响应没有到达客户端时,不能反推服务器未收到正文。
  • 已经标为投递结果未知的记录,若又被来源轮询器按普通失败累计尝试次数,会破坏原有待核查语义。

离线测试可以验证状态分类与处理逻辑;实际投递行为需要独立的端到端验证。

03根因

问题在于失败状态过于粗糙:把“能够确定失败”与“无法确定结果”混在一起,再用同一种重试逻辑处理。业务侧希望只发送一次,但客户端无法凭一次超时证明对方没有接收。

因此必须让传输阶段、明确响应与持久化投递状态共同决定下一步,而不是由 AI 推测是否送达。

04处理

  • 正文前的网络失败、明确的临时拒绝: 在同一次投递内采用有上限的重连与退避。
  • 正文或最终响应阶段超时/断连: 保持“投递结果未知”,进入待核查状态,不盲目重发。
  • 明确的永久拒绝: 记录确定性失败,走有限业务重试和最终失败通知边界。
  • 重试身份: 首次业务邮件重试复用稳定的 Message-ID;该标识便于追踪,本身不保证收件服务器会去重。
  • 来源轮询: 对已有未知结果返回待核查语义,不再反复累计失败尝试。

确定性投递达到重试上限后,应通知人工处理。未知结果不被改写为最终发送失败。

05验证

实践中的离线验证覆盖了 SMTP 阶段分类、有限重连、稳定邮件标识、未知结果状态隔离和最终失败通知。

这些结果用于验证代码处理逻辑,不能作为实际投递成功率。端到端验证还需覆盖服务端响应、状态持久化和通知行为。

06剩余问题

未知结果必须结合服务端记录、收件情况或其他可信证据人工核查。在确认之前,既不能宣称送达,也不能把它当作一定未送达。

应用这套方法时,应在隔离测试链路中验证阶段行为、状态持久化和最终失败通知。离线通过不能直接证明实际投递问题已经解决。