Sign inGet started

从 Amazon SES 迁移

本页将 SES v2 SendEmail 调用、账户级屏蔽列表和 SNS 事件通知映射到 Bird。按顺序执行主迁移指南,并将这些映射用于步骤 1、3 和 4。

交给你的 agent

将本页粘贴到 Claude Code、Cursor 或 Codex 中。agent 会针对你的代码仓库逐步执行本页内容,使用它已有的任何 Bird 接口:已连接的 MCP 服务器,或已安装并登录的 CLI。
代码示例
I am moving an email integration from Amazon SES to Bird. Route through it with me.
1. Check what you already have before setting anything up. If Bird's MCP server is connected, use its tools. If the Bird CLI is installed and signed in, use that. Either one is enough, and every step below is an action you take with whichever you have. Only if neither is present, follow https://bird.com/docs/ai/set-up-your-agent.md to set one up and sign me in. Every Bird docs page serves Markdown at its own URL with `.md` appended, so fetch that rather than the HTML.
2. Read https://bird.com/docs/guides/email/migrate/ses.md for the payload, suppression and event mapping, and https://bird.com/docs/guides/email/migrate.md for the order the steps go in.
3. Find and list my SES usage in this repository and its infrastructure before you change anything: the SendEmail and SendRawEmail call sites through the AWS SDK or CLI, the configuration sets they name, the SNS topics or EventBridge rules carrying my events, the handler subscribed to them, and every identity I send from. Say which of these live in infrastructure code rather than application code, because those change by a different route.
4. Register each of those sending domains with Bird and give me the DNS records to publish, following https://bird.com/docs/guides/email/sending-domains.md. Leave the SES DKIM CNAMEs exactly as they are: Bird's DKIM record uses its own selector, so the two coexist and both providers authenticate side by side until I switch traffic. Publishing DNS affects mail for the whole domain, so show me the records and let me publish them.
5. Export my account-level suppression list from SES and import it into Bird before any production traffic goes through Bird, so my first sends do not reach addresses that already bounced or complained. Read it from GET /v2/email/suppressed-destinations, paginating with NextToken to the end, and keep both the BOUNCE and COMPLAINT reasons. The Bird import takes one address per request and is idempotent, so a partial re-run is safe. https://bird.com/docs/guides/email/suppressions.md has the reason taxonomy.
6. Port the send call and replace the event plumbing. Bird posts signed webhooks straight to an endpoint, so the SNS topic, the subscription-confirmation handshake, and the message-envelope unwrapping all go away rather than being ported: my handler reads the event body directly and verifies it per Standard Webhooks. See https://bird.com/docs/guides/webhooks.md and https://bird.com/docs/guides/email/events.md. Tell me which SNS or EventBridge resources become unused, but do not delete any of them.
7. Run my whole integration against Bird's mail sandbox before any production traffic, following https://bird.com/docs/guides/email/testing-sandbox.md. Sandbox sends run the real pipeline without reaching an inbox or touching my sending reputation.
8. Stop and ask me wherever a step needs a decision. Do not point production traffic at Bird until I have seen the sandbox results and replied with the words cut over to Bird. Retiring the SES path is a separate step that comes later: ask me again and wait for me to reply with the words retire the SES path. A reply that agrees without naming what it is authorising is not authorisation. Finish by telling me what is left that only a person can do.

映射发送调用

SES 将一次发送分散到 DestinationContent 和配置集管道中。我们的 POST /v1/email/messages 是一个扁平载荷:
功能SES (SendEmail v2)Bird
发件人FromEmailAddressfrom
收件人Destination.*Addressesto / cc / bcc(数组)
主题Content.Simple.Subjectsubject
正文Content.Simple.Body.Html/Texthtml / text(至少一个)
Reply-toReplyToAddressesreply_to(数组)
自定义请求头Content.Simple.Headersheaders(string → string 对象)
可筛选标签EmailTagstags: {name, value} 键值对
往返上下文(无)metadata: 任意 JSON
存储模板Content.Templatetemplate + template.parameters
打开/点击追踪配置集track_opens / track_clicks(默认 true
IP 池专用 IP 池(配置集)ip_pool_idipp_...ipp_shared
类别(无)category: marketing(默认)或 transactional
字段上限和默认值(收件人数量、标签和元数据限制)见发送邮件
迁移注意事项:
  • 配置集拆解为逐消息字段。 追踪、IP 池和事件路由在 SES 中属于配置集管辖。这里前两者是载荷字段,事件路由则是 webhook 订阅
  • 认证从 SigV4 变为 bearer token。 无需请求签名;只需一个普通的 Authorization: Bearer bk_... 请求头。从此代码路径中移除 AWS SDK 凭证链。
  • SES 模板迁移为存储模板。 Content.Template(模板名称加 TemplateData)映射到我们的 template 字段,值填入 template.parameters。参见使用模板发送
  • Content.Raw(MIME)没有对应项。 我们从结构化字段构建消息。如果你拼装原始 MIME 来附加文件,请改用我们的附件数组(每个文件一个 base64 content,内联图片使用 content_id)。
  • SES 沙箱 ≠ Bird 沙箱。 SES 的沙箱限制你可以发送给谁。我们的邮件沙箱是一个使用魔法地址的模拟器:无需白名单,也不会真正投递。

导出屏蔽列表

导出账户级屏蔽列表,并通过导入流程处理:
  • GET /v2/email/suppressed-destinations(使用 NextToken 分页,每条记录的原因为 BOUNCECOMPLAINT

转换 webhook 事件

SES 通过 SNS 或 EventBridge 发布事件。我们直接 POST 签名 webhook,因此 SNS 主题、订阅确认握手和消息信封解包都不再需要。事件名称映射如下:
结果SESBird
已接受/已处理Sendemail.acceptedemail.processed
已投递Deliveryemail.delivered
临时失败DeliveryDelayemail.deferred
永久退回Bounceemail.bounced / email.out_of_band_bounce
垃圾邮件投诉Complaintemail.complained
已屏蔽/已抑制(无)email.rejected
打开Openemail.opened
点击Clickemail.clicked
取消订阅Subscriptionemail.list_unsubscribed
email.rejected 是相对 SES 新增的事件:我们以可见方式报告被屏蔽的收件人(状态 rejectedrejection_reason: recipient_suppressed),而不是将其计入发送-退回循环。请为它添加处理逻辑。
我们按照 Standard Webhooks 规范进行签名,而非使用 SNS 消息验证,投递本身带有 HMAC 请求头。验证方法参见 Webhooks 与事件

正式切换

按照主指南完成域名与 DNS沙箱冒烟测试。两者均与服务商无关。DNS 步骤有一条 SES 专属提示:SES 的 DKIM CNAME 在迁移期间保持不变。我们的 DKIM TXT 记录使用独立的选择器,因此两者可以共存。

后续步骤

  • 发送域名:注册、验证生命周期,以及你正在重新指向的 DNS 记录
  • Webhooks 与事件:端点设置和 Standard Webhooks 验证
  • 测试沙箱:在正式切换前对新集成进行冒烟测试
  • 屏蔽列表:确认已导入的列表以及我们此后如何维护它