指南 · SendGrid API

产品团队应如何安全地接入 SendGrid API?

通过服务端邮件服务接入 SendGrid API,使用经过认证的发信域名,以及仅具有 Mail Send 权限的 API 密钥。在调用 `POST /v3/mail/send` 之前校验每封邮件,持久化保存您自己的发送记录,并获取响应中的 `X-Message-ID`。基于原始字节处理带签名的 Event Webhook 负载,对事件去重,并遵守退信、垃圾邮件举报和退订。将 `202 Accepted`、收件服务器投递和进入收件箱视为不同的状态,并且只对暂时性失败进行有上限的重试。

定义一个范围明确且正当的发送任务

SendGrid 的 v3 Mail Send API 是一个用于发送出站邮件的服务商端点,而不是通用的用户邮箱。请将它放在可信的应用服务或队列后台任务之后,并定义哪些产品事件可以生成邮件,例如账户验证、收据、安全通知或用户请求的通知。不要把服务商密钥暴露给浏览器、移动客户端、模板、提示词或日志。在数据模型层面将事务性邮件与依赖许可的营销活动分开,这样收件人预期、偏好处理和信誉就可以独立运营。在实施之前,确定发信域名归谁所有、谁审批模板、哪些环境可以向外部发信,以及开发环境中允许哪些收件人。这一范围将成为 API 密钥权限、域名配置、审计记录、告警和事故响应的边界。它也让更换服务商成为可能,因为产品代码请求的是一个经过批准的邮件操作,而不是在应用各处随意构造 SendGrid 请求。

为专用的发信域名完成身份认证

为您控制的域名或专门用途的子域名配置 SendGrid Domain Authentication,然后严格按照为该身份生成的 DNS 记录进行发布,并在 SendGrid 中完成验证。服务商文档指出,子域名不会继承父域名已认证的身份,因此请验证 From 地址中实际使用的域名。在修改 DNS 之前,先审查现有的 SPF 和 DMARC 记录;不要为同一个主机名再创建第二条 SPF 策略,也不要在未经所有者同意的情况下替换组织现有的 DMARC 策略。当事务性流量和推广流量的受众与风险不同时,有意识地为它们选择不同的身份。在收到的测试邮件中确认可见的 From 地址、Return-Path、DKIM 签名域名、回复路径以及链接品牌化的行为。身份认证建立的是经过授权的身份和对齐信号,但并不决定收件系统最终把邮件放在哪个文件夹。DNS 验证成功之后,仍需持续监控退信、投诉、收件人预期和内容。

为每个环境签发最小权限的 API 密钥

创建一个 Custom Access API 密钥,只授予工作负载所需的权限,对于发信后台任务通常就是 Mail Send 权限。不要给日常发信程序授予对模板、抑制列表、团队成员、统计数据、IP 配置或账户管理的 Full Access 权限。为开发、预发布和生产环境使用不同的密钥,并在名称中标明所属服务和轮换用途。SendGrid 只会显示一次新密钥,因此请将其直接放入该环境的机密管理器中,绝不要复制到源代码管理或共享文档中。在运行时,从由机密存储支持的配置中读取它,并且只通过 HTTPS 在 `Authorization: Bearer` 请求头中传递。将密钥轮换作为一个运维流程来测试:创建一个具有同样窄权限的替换密钥,部署替换密钥,确认受控流量发送成功,然后吊销旧密钥。针对意外的 401 或 403 响应设置告警,因为它们可能表示密钥缺失、凭据已被吊销、权限不匹配或存在不安全的配置变更。

构建并记录每一个 Mail Send 请求

在联系 SendGrid 之前,先创建一条内部的出站记录。为它分配稳定的应用事件键、租户、发件人身份、经过批准的收件人、邮件类别、模板版本和状态。基于这条记录构造服务商负载,使用 `personalizations`、`from`、`subject`,以及至少一个受支持的内容部分或一个经过批准的动态模板。在发起网络调用之前,校验地址语法、收件人数量、附件大小、模板数据和自定义邮件头。SendGrid 当前的 Mail Send 概览规定,包括附件在内的请求总大小必须小于 30 MB,To、Cc 和 Bcc 的收件人总数不得超过 1,000。规模较小、用途明确的请求更容易审计和恢复。收到 `202 Accepted` 响应时,获取 `X-Message-ID` 响应头并将其关联到出站记录。不要在 categories 或 unique arguments 中放入个人数据;SendGrid 警告,这些值可能会被保留,并在邮件内容所应有的保护之外被查看。

验证并处理 Event Webhook

将 SendGrid Event Webhook 配置在一个能够保留原始请求体的 HTTPS 端点上。启用加密签名、OAuth 2.0 或两者同时启用。对于带签名的推送,在解析 JSON 之前,先基于确切的原始字节验证时间戳和 `X-Twilio-Email-Event-Webhook-Signature`;Twilio 警告,重新序列化负载可能改变字节,导致验证失败。拒绝未经身份验证的输入,设置合理的请求大小上限,并根据团队选定的时间戳策略防止重放。验证通过后,先将事件批次入队或持久化保存,再返回成功。使用 `sg_event_id` 去重,然后关联 `sg_message_id`、已存储的 `X-Message-ID` 以及一个不含敏感信息的内部关联值。让状态转换保持单调,这样延迟到达的 processed 事件就不会覆盖之后的 delivered 或退信结果。将原始服务商事件保存在访问受限的存储中以便排查问题,但地址、响应文本和互动数据的保留应尽量限制在产品和政策实际需要的范围内。

精确地为受理、投递和收件箱位置建模

SendGrid 返回的 HTTP `202 Accepted` 表示请求已被受理并排队等待处理,并不表示目标方已受理邮件。`processed` Webhook 事件表示 SendGrid 已受理邮件并可以尝试投递。`delivered` 事件表示 SendGrid 报告收件邮件服务器已受理该邮件,通常附带一个 SMTP 响应。但这仍然不能确定邮件进入了收件箱,因为收件系统可能会把已受理的邮件归类到收件箱的某个标签页、隔离区、垃圾邮件文件夹或其他位置。请在存储和用户界面中将这些状态分开:已请求、服务商已受理、已处理、已延迟、收件服务器已受理、已退信、已丢弃、已投诉或已抑制。不要把每一个非错误的 HTTP 响应都转换为“已送达”。打开等互动信号同样不能证明已送达,而且可能受到隐私功能的影响。精确的状态名称能让支持排查、重试和送达率决策更加安全。

在重试之前先对失败进行分类

按类别处理服务商错误,而不是对每个非 202 响应都进行重试。400 通常需要修正负载、发件人、模板数据或保留的邮件头。401 指向身份验证问题;403 可能表示权限不足或账户策略限制;413 则需要减小邮件大小。SendGrid 在文档中说明了各端点的速率限制响应头,并在刷新周期内的额度耗尽时返回 429,因此请等到重置时间后再重试,并加入随机抖动,而不是制造同步的重试。对 5xx 和传输失败使用指数退避、有限的尝试次数和运维告警进行重试。结果不明确的超时需要特别小心:即使客户端没有收到响应,服务商也可能已经受理了请求。将出站记录保持在未知状态,查找相关联的事件,并在重新发送之前要求一条明确的核对规则。服务商的 API 并不能免除产品层面防止重复发送的需要。绝不要把已知的永久性退信、无效收件人、已退订或已举报垃圾邮件的目标地址当作暂时性的基础设施错误来重试。

尊重抑制列表和收件人的选择

将 bounce、dropped、spam-report、unsubscribe 和 group-unsubscribe 事件纳入收件人安全模型。SendGrid 支持全局抑制,以及针对不同邮件类别的退订组。将每封推广邮件或可选邮件关联到正确的组,提供易于理解的偏好设置途径,并在相关抑制生效时停止发送。不要把绕过抑制的选项当作常规的投递手段。对产品至关重要的邮件可能需要一份单独成文的法律和运营策略,但该策略不应悄悄地覆盖某人对推广邮件的选择或服务商的信誉保护措施。对用于移除抑制记录的支持工具实施强授权,要求填写可见的原因,并保留审计记录。分别追踪永久性和暂时性的投递失败,并在下一次发送之前复核任何人工重新启用的操作。这些控制既保护收件人,也减少了向已经拒收或拒绝该流量的目标地址重复尝试。它们还能防止事务性发信沿袭营销活动中不安全的做法。

在生产流量之前测试完整的生命周期

先使用非生产环境的 SendGrid 密钥和一个受控的已认证子域名。验证 DNS,然后向团队自己拥有的收件箱发送纯文本和 HTML 版本的邮件。确认 `202` 响应和 `X-Message-ID`,并核实带签名的 Webhook 事件能与本地出站记录关联。在不使用真实客户地址的前提下,演练无效负载、已吊销密钥、权限缺失、附件超大、速率限制、延迟、退信、丢弃以及重复事件等路径。确认 Webhook 验证会拒绝被修改过的请求体,并且处理程序只在持久化保存之后才确认。测试密钥轮换、模板回滚、抑制执行以及结果不明确的客户端超时。为请求失败、事件延迟、延迟投递、退信、垃圾邮件举报和 Webhook 签名失败添加仪表盘,其中包含租户和消息标识符,但不包含凭据或完整内容。最后,在上线时重新查阅 SendGrid 的最新文档和账户限制,因为方案权益、区域功能、配额和服务商政策都可能独立于应用代码而变化。

比较服务商特定依赖项

当团队有意依赖 SendGrid 特定的请求字段、模板、账户控制、Webhook 格式、抑制记录和运营归属时,直接 SendGrid 集成是合适的。SendHQ 的公开文档描述了限定工作区作用域的邮件 API,具有已验证域名发送、入站邮件、托管模板、投递事件、抑制记录和 Web 控制台。迁移前,请审查两个服务商的请求体、事件、身份控制、抑制记录、区域要求和已存储的服务商标识符。

常见问题

SendGrid 返回 202 Accepted 是否意味着邮件已送达?

不是。它表示 SendGrid 已受理该 API 请求并将进行处理。请使用 Event Webhook 的投递事件来了解收件服务器是否已受理邮件,并将是否进入收件箱作为另一个独立的结果,API 响应无法确定这一点。

SendGrid 发信密钥应该具有哪些权限?

使用一个 Custom Access 密钥,并将其限制为后台任务所需的 Mail Send 能力。日常发信不要使用 Full Access,并且为开发、预发布、生产、管理以及其他权限差异明显的工作负载分别使用由机密系统管理的密钥。

应如何验证 SendGrid Event Webhook 的签名?

保留确切的原始 HTTP 请求体,读取 Twilio 的签名和时间戳请求头,并在解析 JSON 或重新序列化之前完成验证。实施防重放保护,拒绝验证失败的请求,然后先持久化保存或入队事件批次,再确认接收。

产品应该重试每一个失败的 Mail Send 请求吗?

不应该。对于负载、身份验证、授权、大小和永久性收件人错误,应当修正问题而不是重试。遇到 429 响应时,等到文档规定的重置时间后再重试;对暂时性的网络故障和 5xx 失败进行有上限的退避重试;并在重新发送之前核对结果不明确的超时。

事务性邮件可以绕过 SendGrid 的抑制列表吗?

SendGrid 提供了绕过控制,但产品不应常规性地使用它们。将邮件类别分开,遵守适用的退订或抑制,并对任何例外的重新启用或针对特定策略的发送决定要求成文的授权和审计历史。

在比较 SendGrid 和 SendHQ 前,团队应评估什么?

规划迁移前,请比较服务商的请求体、事件、身份控制、抑制记录、区域要求和已存储的服务商标识符。

参考来源