Sign inGet started

从 MailerSend 迁移

本页将 MailerSend 的发送载荷、屏蔽列表和 webhook 映射到 Bird。请按顺序执行主迁移指南,并在步骤 1、3 和 4 中使用这些映射。
MailerSend 维护五个屏蔽列表,其中一个在设计上是临时的。请在步骤 3 之前阅读导出屏蔽列表:导入该列表会将 72 小时的暂停变为永久屏蔽。

交给你的 agent

将此内容粘贴到 Claude Code、Cursor 或 Codex 中。Agent 会针对你自己的代码仓库逐步处理本页内容,使用它已有的任何 Bird 接口:已连接的 MCP 服务器,或已安装并登录的 CLI。
代码示例
I am moving an email integration from MailerSend 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/mailersend.md for the payload, suppression and webhook mapping, and https://bird.com/docs/guides/email/migrate.md for the order the steps go in.
3. Find and list my MailerSend usage in this repository before you change anything: calls to /v1/email and any SDK wrappers around them, whether I send with template_id and personalization or with html, the webhook handler and the URL it is registered at, and every domain I send from. Tell me the list before you edit anything.
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 MailerSend 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 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. MailerSend keeps five lists under /v1/suppressions: hard bounces, spam complaints, unsubscribes and the blocklist are the four to carry over. Do NOT import the On Hold list: it is a temporary 72-hour hold that MailerSend clears by itself, and importing it would suppress those addresses permanently. 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. Both providers sign with HMAC-SHA256, so this is a re-implementation rather than new code: MailerSend sends one Signature header, Bird follows the Standard Webhooks scheme with webhook-id, webhook-timestamp and webhook-signature, and the signed string is constructed differently. Keep the constant-time comparison. 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 MailerSend path is a separate step that comes later: ask me again and wait for me to reply with the words retire the MailerSend 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.

映射发送调用

MailerSend 的 POST /v1/email 和我们的 POST /v1/email/messages 结构相似。值得修改代码的差异在于标签上限和每个收件人数据的传递方式。
功能MailerSendBird
发件人from ({email, name})from
收件人to(最多 50 个)、cc / bcc(各最多 10 个)to / cc / bcc(数组)
主题subject(最多 998 个字符)subject
正文html / texthtml / text(至少一个)
Reply-toreply_to{email, name}reply_to(数组)
自定义请求头headers{name, value},更高级别套餐)headers(string → string 对象)
可筛选标签tags(字符串数组,最多 5 个)tags: {name, value} 键值对
已存储模板template_id + personalizationtemplate + template.parameters(见下文)
附件attachmentscontentfilenamedispositionidattachments
定时发送send_at(最多提前 72 小时)scheduled_at
批量优先级precedence_bulk(无对应字段,见下文)
类别(无)category: marketing(默认)或 transactional
线程in_reply_toheaders
字段上限和默认值(收件人数量、标签和元数据限制)详见发送邮件
迁移说明:
  • MailerSend 中地址是对象,这里是字符串。 {"email": "a@x.com", "name": "A"} 变为 "A <a@x.com>" 或直接写 "a@x.com",适用于 fromtoccbccreply_to
  • tags 是裸字符串,最多五个。我们的标签是键值对。 类似 "welcome" 的标签变为 {"name": "category", "value": "welcome"}。选择一个稳定的 name,这样你的仪表盘筛选方式与之前 MailerSend 标签统计一致。
  • personalization 是每个收件人的模板数据。 它是一个以收件人邮箱为键的数组。我们的 template.parameters 应用于整个发送,因此内容确实因收件人而异的消息需要为每个收件人单独发送,或分别作为 batch 中的一个条目。如果你的 personalization 数组对每个收件人都携带相同的值,则可以合并为一个 template.parameters 对象。
  • 往返上下文在 MailerSend 中没有可复制的对应物。 如果你之前从 tags 重建上下文,请改用 metadata:我们会在每个 webhook 事件中将其与 email_id/recipient_id 一起回传,并且不受五个条目的上限。
  • precedence_bulk 没有对应字段,category 也不是。 precedence_bulk 设置一个 Precedence: bulk 请求头,要求自动回复和外出回复代理保持静默。我们的 category 决定哪些屏蔽记录和退订偏好可以拦截消息,仅此而已。用 category 代替它会改变屏蔽行为,对自动回复没有任何作用。如果你需要该请求头,请注意 headers 会拒绝地址和平台名称,但不会拒绝此请求头。
  • 自定义请求头和 list_unsubscribe 在 MailerSend 受套餐限制。 如果你的套餐不包含它们,在这里可以直接使用;参见发送邮件类别了解我们如何在营销邮件中处理 list-unsubscribe。

导出屏蔽列表

MailerSend 在 /v1/suppressions 下维护五个列表,每个列表一个端点。四个是永久的,需要迁移;第五个不可迁移。
导出后通过导入流程处理:
  • 硬退信,例如 GET https://api.mailersend.com/v1/suppressions/hard-bounces
  • 垃圾邮件投诉
  • 退订
  • 黑名单,即你手动添加的地址和模式
不要导入 On Hold 列表。 MailerSend 自己的描述是:它保存的地址 "have soft bounced 5 times within 30 days"、"these emails will be blocked for 72 hours",之后 "automatically removed from the list"。这是一个由提供商自行清除的冷却期,因此导入它会将临时暂停转为永久屏蔽,并悄然停止向即将被释放的地址发送邮件。我们对应的行为是延迟投递,我们根据实时投递结果来处理,而不是从导入的列表中处理。
MailerSend 的列表类型映射到我们的 hard_bouncecomplaintmanual 原因;屏蔽列表中有完整的分类。

转换 webhook 事件

结果MailerSendBird
已接受/已处理activity.sentemail.acceptedemail.processed
已送达activity.deliveredemail.delivered
临时失败activity.soft_bounced / activity.deferredemail.deferred
永久退信activity.hard_bouncedemail.bounced / email.out_of_band_bounce
垃圾邮件投诉activity.spam_complaintemail.complained
打开activity.opened / activity.opened_uniqueemail.opened
点击activity.clicked / activity.clicked_uniqueemail.clicked
退订activity.unsubscribedemail.unsubscribed / email.list_unsubscribed
临时暂停recipient.on_hold_added / ..._removed(无对应事件;见上文)
两个差异决定了你的处理程序需要改动多少。
双方都使用 HMAC-SHA256 签名,因此这是重新实现而非编写全新代码。 MailerSend 发送一个 Signature 请求头,其中包含用该 webhook 签名密钥计算的载荷哈希。我们遵循 Standard Webhooks 方案,使用 webhook-idwebhook-timestampwebhook-signature,对由 id、时间戳和正文构建的字符串进行签名,因此时间戳还提供了重放保护。保留你已有的常量时间比较逻辑,替换构造方式即可;具体做法参见 Webhooks 和事件
MailerSend 区分打开/点击与其唯一变体,我们不区分。 activity.openedactivity.opened_unique 都以 email.opened 形式到达,因此之前只计唯一变体的处理程序需要自行基于 recipient_id 去重。我们的投递事件以收件人为范围,因此发送给三个收件人会产生三个投递结果,而不是一个。

切换上线

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

后续步骤

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