Sign inGet started

从 Mandrill 迁移

本页将 Mandrill(Mailchimp Transactional)的 messages/send 载荷、拒绝黑名单和 webhook 映射到 Bird。按顺序阅读主迁移指南,并在步骤 1、3 和 4 中使用这些映射。
迁移中有两个主要的结构变化。Mandrill 将所有内容嵌套在 message 对象下,并在请求体中使用 key 进行认证。我们采用扁平的顶层载荷和标准的 Authorization: Bearer 请求头。此外,Mandrill 的收件人 type(每个地址上以字段形式标记 to/cc/bcc)在我们这里变为独立的 to/cc/bcc 数组。

交给你的 agent

将本页内容粘贴到 Claude Code、Cursor 或 Codex 中。agent 会根据你的代码仓库逐步处理本页内容,使用它已有的任何 Bird 接口:已连接的 MCP 服务器,或已安装并登录的 CLI。
代码示例
I am moving an email integration from Mandrill 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/mandrill.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 Mandrill usage in this repository before you change anything: the messages/send and messages/send-template call sites and any SDK wrappers around them, the webhook handler and the URL it is registered at, and every domain I send from. Mandrill authenticates with an API key passed in the request body rather than a header, so tell me every place that key appears in my code: the port changes how I authenticate, not just what I send, and that key is a secret currently sitting in a payload.
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 rejects from Mandrill 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. Read the list through rejects/list, and skip the soft-bounce rows: those are transient failures rather than suppressions, and importing them would suppress addresses that are fine. Show me how many rows you skipped. 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. Move the API key out of the request body and into the Authorization header as a bearer token while you are there. Bird signs deliveries per Standard Webhooks rather than Mandrill'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 Mandrill path is a separate step that comes later: ask me again and wait for me to reply with the words retire the Mandrill 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.

映射发送调用

功能Mandrill (messages/send)Bird
认证请求体中的 keyAuthorization: Bearer bk_... 请求头
发件人message.from_email / from_namefrom: 字符串或 { "email", "name" }
收件人message.to: [{ email, name, type }]to / cc / bcc: 按 type 字段拆分
主题message.subjectsubject
正文message.html / message.texthtml / text(至少一个)
Reply-tomessage.headers["Reply-To"]reply_to: 数组
自定义请求头message.headersheaders: string → string 对象
可筛选标签message.tags: 纯字符串tags: { name, value } 键值对
往返上下文message.metadatametadata: 任意 JSON
存储模板messages/send-template + merge_varstemplate + template.parameters
定时发送send_atscheduled_at
打开/点击追踪message.track_opens / track_clickstrack_opens / track_clicks(默认 true
附件message.attachments: { type, name, content }attachments: { content_type, filename, content }
内联图片message.images: { type, name, content }attachmentscontent_id
类别subaccount / tags 约定category: marketing(默认)或 transactional
字段上限和默认值(收件人数量、标签和元数据限制)详见发送邮件
迁移注意事项:
  • 扁平化并重新认证。 去掉 message 包装(其字段移至顶层),将 API 密钥从请求体移至 Authorization 请求头。key 字段在我们这里没有对应项。
  • type 拆分收件人。 Mandrill 在地址对象上将每个收件人标记为 toccbcc。我们使用三个独立的数组。迁移时按 type 字段对 to 列表进行分组。
  • 标签变为名称/值键值对。 Mandrill 的标签是纯字符串("welcome")。我们的标签是 { name, value } 键值对。选一个固定的 name,例如 { "name": "category", "value": "welcome" },以保持筛选和分析分组与 Mandrill 统计数据一致。metadata 可以直接作为 JSON 迁移。
  • 存储模板可以迁移,但有一个注意点。 Mandrill 的 messages/send-template 变为我们的 template 字段(通过 ID 或 slug 引用),变量值放在 template.parameters 中。参见使用模板发送。我们的模板变量是每条消息级别而非每个收件人级别,因此每个收件人的 merge_vars 需要变为批量中的一条记录,每条记录有自己的 parameters
  • 附件可直接迁移。 Mandrill 的 base64 content 对应我们的 content,内联的 images(在 HTML 中以 cid: 引用)变为带有 content_idattachments 条目。参见附件

导出屏蔽列表

Mandrill 将不需要的地址保存在其拒绝黑名单中。通过 rejects/list API 拉取(或从 Rejection Blacklist 视图导出)。每条记录有一个原因(hard-bouncesoft-bouncespamunsubcustom)。跳过 soft-bounce 行,因为它们描述的是暂时性失败而非真正的屏蔽。将其余记录通过导入流程处理。

转换 webhook 事件

Mandrill 以批量事件数组的形式发送 webhook;将 event 值映射到 Bird 的事件词汇表
结果MandrillBird
已发送 / 已接受sendemail.acceptedemail.processed
已投递deliveredemail.delivered
临时失败deferralemail.deferred
永久退信hard_bounceemail.bounced / email.out_of_band_bounce
软退信soft_bounceemail.deferred(放弃重试后变为 email.bounced
垃圾邮件投诉spamemail.complained
退订unsubemail.unsubscribed / email.list_unsubscribed
拒绝/阻止rejectemail.rejected
打开openemail.opened
点击clickemail.clicked
需要在代码中处理两个差异:
  • 接受在我们这里拆分为两个事件。 Mandrill 的 send 表示消息已注入,delivered 表示接收服务器已接收,这与我们的区分方式相同。区别仅在我们这一侧:我们在 email.delivered 之前将接受(email.accepted)和处理(email.processed)分开,因此以前仅监听 send 的处理逻辑现在需要在两个事件之间选择。
  • 事件按收件人划分,且签名方式不同。 我们的投递事件包含 recipient_idemail_id,每个收件人一个事件流。我们每次请求发送一个事件,并按照 Standard Webhooks 规范签名。Mandrill 则使用 X-Mandrill-Signature HMAC 对批量数组签名。参见 Webhooks & events 了解验证方法。

正式切换

按照主指南完成域名与 DNS沙箱冒烟测试。两者都与提供商无关。

后续步骤

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