Bird

从 SendGrid 迁移

本页将 SendGrid 的 v3 Mail Send 载荷、屏蔽列表和 Event Webhook 映射到 Bird。按顺序遵循主迁移指南,并在步骤 1、3 和 4 中使用这些映射。

将此交给你的 agent

将此内容粘贴到 Claude Code、Cursor 或 Codex 中。Agent 会根据你的代码仓库逐步处理本页内容,使用它已有的任意 Bird 接口:已连接的 MCP 服务器,或已安装并登录的 CLI。
代码示例
I am moving an email integration from SendGrid 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/sendgrid.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 SendGrid usage in this repository before you change anything: the /v3/mail/send call sites and any SDK wrappers around them, the Event Webhook handler and the URL it is registered at, and every domain I send from.
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 every DNS record my current provider uses exactly as it is: Bird's records are published alongside them 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 suppressions from SendGrid and import them into Bird before any production traffic goes through Bird, so my first sends do not reach addresses that already bounced or complained. SendGrid splits these across GET /v3/suppression/bounces, GET /v3/suppression/spam_reports, GET /v3/suppression/unsubscribes, and GET /v3/asm/groups/{group_id}/suppressions for each unsubscribe group worth carrying over. 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 the webhook handler using the mapping tables on the provider page. Bird signs deliveries per Standard Webhooks rather than SendGrid's scheme, so treat verification as a rewrite rather than a URL change: https://bird.com/docs/guides/webhooks.md and https://bird.com/docs/guides/email/events.md.
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 SendGrid path is a separate step that comes later: ask me again and wait for me to reply with the words retire the SendGrid 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.

映射发送调用

SendGrid 的 POST /v3/mail/send 将收件人封装在 personalizations 数组中。我们的 POST /v1/email/messages 是扁平载荷,因此每个 personalization 会变成独立的发送请求(或一个批量条目)。
功能SendGridBird
发件人from.emailfrom
收件人personalizations[].to / cc / bccto / cc / bcc(数组)
主题subjectsubject
正文content[](type + value)html / text(至少一个)
回复地址reply_to / reply_to_listreply_to(数组)
自定义请求头headersheaders(string → string 对象)
可筛选标签categoriestags: {name, value} 键值对
往返上下文custom_argsmetadata: 任意 JSON
存储模板template_id + dynamic_template_datatemplate + template.parameters
定时发送send_atscheduled_at
打开/点击跟踪tracking_settingstrack_opens / track_clicks(默认 true
IP 池ip_pool_nameip_pool_idipp_...ipp_shared
类别(无)category: marketing(默认)或 transactional
字段上限与默认值(收件人数量、标签和元数据限制)详见发送邮件
迁移说明:
  • categories 是裸字符串,我们的标签是键值对。 类似 "welcome" 的 category 变为 {"name": "category", "value": "welcome"}。选择稳定的 name,使仪表盘的筛选方式与 SendGrid 中的统计一致。
  • custom_args 会在每个事件中回传。我们的 metadata 同理。 我们会在每个 webhook 事件中随 email_id/recipient_id 一起回传你的 metadata(和 tags),你的处理程序无需额外查询即可获取上下文。
  • 动态模板迁移为存储模板。 template_iddynamic_template_data 变为 template(通过 ID 或 slug 引用)加 template.parameters,在同一发送调用中使用。参见使用模板发送send_at 直接映射到 scheduled_at
  • 附件直接迁移。 SendGrid 的 attachments(base64 contenttypefilename、用于内联的 content_id)逐字段映射到我们的附件数组。
  • 退订组(asm 无法作为概念直接迁移:我们在类别层级处理 list-unsubscribe,因此 marketing 邮件会自动获得感知屏蔽的退订处理。

导出屏蔽列表

SendGrid 将屏蔽列表分散在多个端点中;逐个导出,然后通过导入流程处理:
  • GET /v3/suppression/bounces
  • GET /v3/suppression/spam_reports
  • GET /v3/suppression/unsubscribes(全局退订)
  • GET /v3/asm/groups/{group_id}/suppressions(针对每个需要迁移的退订组)

转换 webhook 事件

结果SendGrid Event WebhookBird
已接受/已处理processedemail.acceptedemail.processed
已投递deliveredemail.delivered
临时失败deferredemail.deferred
永久退信bounceemail.bounced / email.out_of_band_bounce
垃圾邮件投诉spamreportemail.complained
已拦截/已屏蔽droppedemail.rejected
打开openemail.opened
点击clickemail.clicked
退订unsubscribe / group_unsubscribeemail.unsubscribed / email.list_unsubscribed
droppedemail.rejected 的等价关系是需要重点测试的:与 SendGrid 一样,我们会显式报告被屏蔽的收件人(状态 rejectedrejection_reason: recipient_suppressed),而不是静默丢弃,因此你的审计逻辑可以顺利迁移。
验证方式的变化不仅限于事件名称:SendGrid 的 Event Webhook 使用 ECDSA 公钥签名,而我们按照 Standard Webhooks HMAC 方案签名。将你的验证代码替换为 Webhooks 与事件中的示例。SendGrid 还会将事件批量打包为 JSON 数组,而我们每个请求只投递一个事件。

正式切换

按照主指南中的域名与 DNS沙箱冒烟测试逐步操作。两者均与服务商无关。

后续步骤

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