Bird

从 Mailgun 迁移

本页将 Mailgun 的 POST /v3/{domain}/messages 参数、屏蔽列表和 webhook 事件映射到 Bird。请按顺序执行主迁移指南,并在步骤 1、3 和 4 中使用这些映射。

交给你的 agent

将本页粘贴到 Claude Code、Cursor 或 Codex 中。Agent 会对照你自己的代码仓库逐步处理本页内容,使用它已有的任何 Bird 接口:已连接的 MCP 服务器,或已安装并登录的 CLI。
代码示例
I am moving an email integration from Mailgun 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/mailgun.md for the parameter, 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 Mailgun usage in this repository before you change anything: the /v3/{domain}/messages call sites and any SDK wrappers around them, every o:, v: and h: prefixed parameter I pass, my webhook handler and the URL it is registered at, and every Mailgun domain I send from. Mailgun scopes almost everything per domain, so keep that list of domains: the next two steps both work through it.
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 Mailgun 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 Mailgun 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. Mailgun keeps three lists per domain, so pull GET /v3/{domain}/bounces, GET /v3/{domain}/complaints and GET /v3/{domain}/unsubscribes for every domain you found. 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. Two things need attention rather than translation: Mailgun reports one failed event with a severity field where Bird has separate deferred and bounced events, and Bird signs deliveries per Standard Webhooks rather than Mailgun's scheme, so treat verification as a rewrite. See 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 Mailgun path is a separate step that comes later: ask me again and wait for me to reply with the words retire the Mailgun 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.

映射发送调用

Mailgun 的表单编码参数前缀(o: 选项、v: 变量、h: 请求头)全部变为 POST /v1/email/messages 上的一级 JSON 字段:
功能MailgunBird
发件人fromfrom
收件人to / cc / bccto / cc / bcc(数组)
主题subjectsubject
正文html / texthtml / text(至少提供一个)
回复地址h:Reply-Toreply_to(数组)
自定义请求头h:X-*headers(string → string 对象)
可筛选标签o:tagtags: {name, value} 键值对
往返上下文v:* / X-Mailgun-Variablesmetadata: 任意 JSON
存储模板template + t:variablestemplate + template.parameters
定时发送o:deliverytimescheduled_at
打开/点击追踪o:tracking-opens / o:tracking-clickstrack_opens / track_clicks(默认 true
类别(无)category: marketing(默认)或 transactional
字段上限和默认值(收件人数量、标签和元数据限制)详见发送邮件
迁移说明:
  • 请求变为 JSON。 Mailgun 接受 multipart form data,我们接受带有 Content-Type: application/json 的 JSON 正文。这通常是迁移中最大的机械性改动。
  • v: 变量会在事件中回传。我们的 metadata 也是如此。 我们会在每个 webhook 事件中将你的 metadata(和 tags)与 email_id/recipient_id 一起回传,因此你的处理程序无需额外查询即可获取它们。
  • 收件人变量无法一对一迁移。 Mailgun 的 recipient-variables 可在一次调用中为多个收件人做个性化。在这里,该功能由批量端点承担,每个收件人一条记录,各自拥有自己的内容或用于 {{ token }} 替换的 parameters 值。
  • 存储模板可直接迁移。 Mailgun 的 template 参数映射到我们的 template 字段,变量值放在 template.parameters 中。参见使用模板发送
  • 附件可直接迁移。 Mailgun multipart 的 attachment / inline 文件变为我们的 attachments 数组,使用 base64 content(内联图片请设置 content_id)。参见附件

导出屏蔽列表

Mailgun 为每个域维护三个列表。逐一导出,并通过导入流程处理:
  • GET /v3/{domain}/bounces
  • GET /v3/{domain}/complaints
  • GET /v3/{domain}/unsubscribes
对每个发送域重复此操作。Mailgun 的列表以域为范围,而我们的屏蔽列表以工作区为范围,因此你需要导入所有域列表的合集。

转换 webhook 事件

Mailgun 用一个 failed 事件加 severity 字段来区分临时故障和永久故障。我们将它们拆分为:
结果MailgunBird
已接受/已处理acceptedemail.acceptedemail.processed
已投递deliveredemail.delivered
临时故障failed(临时)email.deferred
永久退信failed(永久)email.bounced / email.out_of_band_bounce
垃圾邮件投诉complainedemail.complained
已屏蔽/已抑制(无)email.rejected
打开openedemail.opened
点击clickedemail.clicked
退订unsubscribedemail.list_unsubscribed
email.rejected 在 Mailgun 中没有对应项:我们会显式报告被屏蔽的收件人(状态 rejectedrejection_reason: recipient_suppressed),而不是静默跳过。请为此事件添加处理程序,不要将其当作退信处理。
验证方式也有变化:Mailgun 在载荷的 signature 对象内使用基于 timestamp + token 的 HMAC 进行签名,而我们按照 Standard Webhooks 规范,使用请求头而非载荷字段进行签名。请将你的验证代码替换为 Webhooks & events 中的方案。

正式切换

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

后续步骤

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