指南 · 邮件 API

产品团队应如何安全地实施邮件 API?

应把邮件 API 实现为一个带权限控制的异步工作流,而不是从表单直接调用服务商。先验证调用方身份,确认租户拥有已验证的 From 域名,校验邮件并检查其大小,分配一个稳定的应用任务 ID,只入队一次,再由后台任务提交。在服务商受理后记录其消息 ID,以幂等方式接收投递事件,并将永久性退信和投诉加入抑制列表。只有在重复发送风险可控时才进行有上限的重试。将凭据保留在服务端,尽量减少日志中的邮件数据,并区分 API 受理、邮件服务器投递和进入收件箱这几个不同的结果。

在选择服务商之前先定义 API 边界

邮件 API 应当表达应用的意图,而不是把服务商的每个细节都泄漏到产品代码中。为邮件、发信域名、API 密钥、事件和抑制记录定义资源。决定调用方可以控制哪些字段,包括 From、To、Reply-To、主题、纯文本、HTML,以及一个很小的邮件头允许列表。拒绝调用方提供的、可能与服务商签名或路由冲突的传输类邮件头。把发送当作一次有后果的写操作:响应应当标识一个应用级的邮件资源及其当前状态,而不是暗示邮箱层面的结果。把服务商账户、区域、配置集和传输标识符隐藏在适配层之后。这条边界让更换服务商成为可能,也为授权、数据保留和防滥用控制提供了一个稳定的落脚点。

验证调用方身份,并对每个发件域名进行授权

API 密钥只以单向哈希的形式存储,完整的密钥只显示一次。为每个密钥设置所属工作区、状态、创建时间和吊销途径;当某个集成只应发送或只应读取事件时,添加更窄的作用域。身份验证回答的是“谁出示了凭据”,而授权决定该主体是否可以使用所请求的 From 域名和邮件资源。每次发送(包括批量端点)都要检查域名所有权,而不是信任客户端提供的域名标识符。在开放生产流量之前,要求先完成服务商验证。绝不要把服务商凭据或工作区 API 密钥放在浏览器 JavaScript、查询字符串、分析数据或错误信息中。在多租户 API 中,对邮件、事件、抑制记录、收件箱和域名标识符进行对象级授权尤其重要。

验证域名并对齐身份验证

一个发信域名需要的不只是数据库里的一个标志位。完成服务商的所有权检查,并发布所需的 DKIM 记录。SPF 为 SMTP MAIL FROM 或 HELO 身份授权主机,而 DKIM 通过加密的邮件签名把签名域名与邮件关联起来。DMARC 评估通过验证的 SPF 或 DKIM 标识符是否与可见的 RFC 5322 From 域名对齐,并允许域名所有者发布处理策略和报告策略。如果域名已经有 SPF 记录,请把所需的机制合并到现有记录中;RFC 7208 规定,一个域名不得发布会导致选出多于一条 SPF 记录的多条记录。只有在受控测试邮件和汇总报告都表明每个合法发件方均已对齐之后,才推行更严格的 DMARC 策略。身份验证可以减少域名被未授权使用,但并不能确保邮件进入收件箱。

校验邮件结构,并尽量缩小可接受的输入

RFC 5322 将互联网邮件定义为一组邮件头字段加上可选的正文,MIME 规范则将内容扩展到基本文本之外。API 可以隐藏大部分线上格式细节,同时仍然严格执行这些格式。规范化收件人数组,限制收件人数量和编码后的总大小,要求至少有纯文本或 HTML 正文之一,并校验地址格式,但不要假装语法正确就能证明邮箱存在。从将成为邮件头的字段中移除回车和换行字符。自行生成 Message-ID 或交给服务商生成;不要把它复用为应用任务 ID,因为邮件的新版本完全可能获得新的标识符。只允许有文档说明的自定义邮件头,拒绝与受保护字段重复的邮件头,并在提交给服务商之前渲染模板,这样缺失变量会在受控的应用状态中失败。

只入队一次,并使用稳定的应用标识符

一次用户请求应当在一个事务中创建一个持久化的邮件任务,然后由后台任务调用服务商。为该任务分配稳定的标识符;如果接口约定支持,还要记录请求指纹或调用方提供的幂等键。HTTP 将 POST 默认定义为非幂等,并告诫不要自动重试,除非客户端知道该操作实际上是幂等的,或者知道原请求并未被执行。这一点对邮件很重要,因为超时可能发生在服务商已受理邮件、但后台任务尚未收到响应之时。遇到结果不明确的失败时,应先核对已存储的任务和服务商的状态,而不是发起一次新的发送。当应用状态和队列发布必须同步变更时,使用发件箱模式(outbox pattern),并在幂等边界上加一个唯一性约束。

按失败类别设计重试

区分校验失败、授权失败、限流、服务商拒绝、暂时性传输失败和收件人投递失败。无效输入和未授权的 From 域名应当直接失败,不做重试。服务商的速率限制和临时性服务错误可以重试,但要使用有上限的指数退避、随机抖动、尝试次数上限,以及比后台任务请求截止时间更长的队列可见性超时。结果不明确的网络超时需要进行能识别重复的核对,而不是无条件地发起新请求。SMTP 本身区分暂时性的 4xx 回复和永久性的 5xx 回复,但使用服务商 API 的应用应当遵循该服务商文档中的错误语义。将重试耗尽的任务移入可复核的死信状态,并保留经过脱敏的原因。不要像处理 API 故障那样重试永久性的收件人退信,也不要把一次投诉变成又一次发送尝试。

记录受理结果并接收投递事件

服务商受理后立即持久化其邮件标识符,并将其映射到应用邮件 ID。即使投诉报告隐去了收件人详情,服务商事件也能随后更新正确的资源。例如,Amazon SES 区分成功发送和向收件人的邮件服务器投递,并可发布投递、退信、投诉、拒绝、投递延迟、渲染失败、打开和点击事件。使用服务商记录的机制验证 Webhook 真实性,验证事件结构,按服务商事件标识符或确定性指纹去重,并允许重复投递同一事件而不重复副作用。仅在必要时存储原始负载,并对其加密、实施访问控制和保留期限限制。规范化状态应区分已受理、已投递至服务器、已退信、已投诉、已延迟、已拒绝和已抑制结果。

让抑制列表成为发送时的控制手段

每次提交给服务商之前都应检查抑制记录,而不只是在控制台中展示它。永久性退信的地址和投诉通常需要加入抑制列表;暂时性的投递延迟则需要不同的策略。有意识地确定抑制的范围。账户级列表可以保护共享的信誉,但可能导致一个租户的收件人结果阻断另一个租户。租户级列表降低了这种耦合,但仍然需要一个防滥用和平台安全层。记录原因、来源事件、租户、创建时间和受控的移除途径。移除投诉或永久性退信的抑制记录是有后果的操作,应当经过审慎的复核,并有证据表明该地址有效且收件人期待收到这封邮件。避免把原始收件人地址复制到通用日志或实验中;运营存储可以用来执行发送策略,而分析只使用汇总计数。

保护批量发送和敏感的业务流程

批量端点会放大授权错误或校验错误的影响。对每一项都执行同样的域名所有权、抑制列表、大小和内容检查,严格限制单批的最大条数,并返回逐项结果,同时不泄露其他租户的数据。速率限制应当同时存在于凭据、工作区、域名和服务商各个层级,并对突发流量和滚动窗口内的发送量分别控制。单一的全局每秒请求数限制是不够的,因为一个请求可能包含很多收件人。在由智能体驱动的工具中,提交高影响的批量发送之前要求明确确认。当事务性邮件和营销邮件的许可规则和运营规则不同时,将两者的权限分开。监控异常的收件人增长、反复被拒的域名、退信率或投诉率的大幅变化,以及密钥的快速创建。速率限制有助于安全,但它不能取代身份验证、对象级授权、经过验证的许可和滥用响应。

上线前测试各种失败路径

使用服务商的模拟器或受控邮箱,测试受理、投递到收件服务器、硬退信、投诉、延迟、无效域名、已吊销的密钥、限流、服务商超时、重复的 Webhook 以及队列重新投递。确认同一个幂等键只会创建一封应用邮件,重放的事件不会产生重复的副作用,并且一个租户无法读取另一个租户的域名或邮件 ID,也无法用它们发信。检查一封真实收到的邮件的 From、Return-Path、DKIM、SPF、DMARC 对齐、纯文本和 HTML 的渲染效果、适用时的退订行为以及链接。在获准的服务商限制以内对队列进行压力测试,并验证背压机制生效,而不是绕过它。针对队列积压时长、重试耗尽、事件接收失败、配额余量、退信率和投诉率变化以及缺失的服务商回调设置告警。上线检查清单应为每个告警和恢复操作指定负责人。

在 SendHQ 上谨慎地应用这一模式

SendHQ 提供限定工作区作用域的 bearer 密钥、已验证 From 域名检查、单封和批量邮件创建、入站收件箱、邮件事件及抑制资源。这些功能支持本指南中的架构:将密钥保留在服务器端,创建邮件资源,保留其 ID,并读取后续事件,而不是将初始响应视为最终投递。无论使用何种平台,调用方仍应对预期收件人、合法且符合预期的邮件、内容准确性以及对重要发送的审慎审批负责。

常见问题

邮件 API 应该在 Web 请求中同步发送吗?

通常不应该。先创建一个持久化的应用邮件并将其入队,再由后台任务调用服务商。这样可以隔离延迟,支持有上限的重试,也更容易核对结果不明确的服务商响应。

请求超时时,如何防止重复发送邮件?

使用稳定的应用任务 ID,并在幂等边界上加唯一性约束。遇到结果不明确的超时时,先核对现有任务,再决定是否以新的身份向服务商再次提交。

邮件 API 返回成功响应是否意味着已送达?

不是。它通常只表示 API 或服务商已受理该请求。请使用之后的事件,把投递到收件服务器、退信、投诉、延迟、拒绝和抑制与最初的受理区分开来。

邮件 API 需要哪些 DNS 记录?

具体记录取决于服务商,但生产环境发信通常需要域名验证和 DKIM,再加上正确的 SPF 策略,以及与合法发信流对齐的 DMARC 策略。

API 密钥可以存放在浏览器代码中吗?

不可以。将工作区和服务商的凭据保存在服务端的机密存储中,尽可能以哈希形式存储应用 API 密钥,完整的密钥只显示一次,并提供及时吊销和轮换的途径。

邮件 API 应如何处理永久性退信?

规范化服务商事件,将其映射到对应的应用邮件,并在预定范围内阻止之后向该收件人进行常规发送。移除抑制记录应当审慎进行,并有证据支持。

参考来源