Sign inGet Started

发送邮件

POST /v1/email/messages 发送一封邮件。在 JSON 载荷中提供发件人、收件人和内容。API 返回 202 Accepted 及一个消息 ID,然后异步投递邮件。完整的 schema 请参阅 API 参考文档。

最简发送

最小有效载荷包括一个 from、至少一个 to 收件人、一个 subject 以及正文(html、text 或两者)。from 地址必须位于你已在此工作区中验证的域名上,或位于引导域名上。
const msg = await bird.email.send({
  from: { email: "onboarding@messagebird.dev", name: "Bird" },
  to: ["delivered@messagebird.dev"],
  subject: "Hello from Bird",
  html: "<p>My first Bird email.</p>",
});
console.log(msg.id, msg.status); // "em_…", "accepted"
使用你的区域主机(https://us1.platform.bird.com 或 https://eu1.platform.bird.com)并搭配对应的 bk_{region}_... 密钥。
发送示例使用 delivered@messagebird.dev,这是一个始终接收邮件的沙盒地址。API 会以 422 拒绝占位域名:example.com、example.net、example.org、example.edu、test.com,以及保留顶级域 .test、.example、.invalid 或 .localhost 下的任何域名。发送到这些域名只会产生退信,损害你的发件人信誉。

在验证域名之前发送

在引导期间,你可以使用我们共享的引导域名 onboarding@messagebird.dev 发送邮件。这些发送跳过域名检查,但只能送达你工作区中已验证的成员和沙盒地址,且受每日收件人上限限制。快速入门中有具体规则和限制。

构建载荷

收件人

to、cc 和 bcc 各接受最多 50 个地址,且 to 至少需要一个。每个条目可以是纯邮件地址字符串、RFC 5322 邮箱字符串(Jane <jane@acme.com>),或带可选显示名称的对象。
位于工作区屏蔽列表中的收件人不会导致请求失败。请求仍然返回 202,每个被屏蔽的收件人在读取端点上显示为 status: rejected,原因为 recipient_suppressed,即使所有收件人都被屏蔽也是如此。

内容

subject 为内联发送的必填项,最多 998 个字符。提供 html、text 或两者,每项最多 524,288 个字符。建议同时提供两者:无法渲染 HTML 的客户端会回退到纯文本部分。
要个性化内联内容,在主题或正文中放入 {{ variable }} 占位符,并在 parameters 中传递对应的值,序列化后最大 16 KB。一组值覆盖该次发送的所有收件人,没有匹配键的占位符渲染为空。对于重复使用的内容,请改用模板发送。
包含 parameters(即使是空对象 {})即可将主题和正文作为 Liquid 处理。省略它则 {{ animal }} 等占位符原样发送。每个参数名是单个单词,例如 first_name;带点号的名称和保留名称 bird 会被拒绝。无效的 Liquid 语法和不支持的标签或过滤器会返回 422。
插入 HTML 中的值会被转义,因此不会改变周围的标记。对于完整的链接或图片 URL,使用 {{ link }} 而不加 url_encode。对于 URL 查询参数中的值,需要显式编码,例如 https://example.com/search?q={{ query | url_encode }}。

回复地址和自定义请求头

reply_to 接受 1 到 25 个地址,格式与收件人相同。收件人的每次回复都会发送到 all 地址,因此通常设置一到两个。
headers 是一个字符串到字符串的对象,用于你自己的请求头,例如 {"X-Campaign": "spring-2026"},最多 25 个,值最长 998 个字符。以下三类请求头会以 422 返回:
  • 地址和平台请求头。 通过专用字段(from、to、cc、bcc、reply_to、subject)设置消息的寻址信息。这些名称以及我们为你生成的请求头(Content-Type、Content-Transfer-Encoding、DKIM-Signature、Received、Return-Path)不能在此设置。
  • List-Unsubscribe 和 List-Unsubscribe-Post(marketing 发送时)。 我们会为这类发送自动设置符合规范的一键退订请求头。在 transactional 发送中,你设置的值将原样保留。
  • 包含回车符或换行符的任何值。

跟踪

track_opens 和 track_clicks 默认均为 true。将其中任一项设为 false 可跳过此次发送的打开像素注入或链接改写。跟踪与指标介绍了每项设置对消息的具体影响。

类别和 IP 池

category 对内容进行分类并设置屏蔽策略:marketing 在所有屏蔽原因和任何退订情况下阻止投递,transactional 在投诉屏蔽或仅营销类退订时仍会投递(针对所有消息的退订记录同样会阻止投递)。在模板发送中默认使用模板的类别,其他情况默认为 marketing,因此对于收据、密码重置和其他事务性邮件,请显式设置 transactional。类别详述了如何选择。通过 SMTP 提交的邮件则从密钥的 SMTP 配置中获取类别。
ip_pool_id 选择发送池:一个池 ID(ipp_...),或 ipp_shared 以显式使用共享池。省略此项则使用组织的默认池。未知的池或没有可用专用 IP 的池会以 422 被拒绝。

字段参考

字段类型必填限制与说明
fromaddress是必须位于已验证的域名或引导域名上
toaddress[]是1 到 50
cc、bccaddress[]否各最多 50 个
subjectstring内联发送时最多 998 个字符;模板发送时省略
html、textstring至少一项每项最多 524,288 个字符;模板发送时省略
reply_toaddress[]否1 到 25;回复发送至所有列出的地址
headersobject (string → string)否最多 25 个;保留名称会被拒绝(见自定义请求头)
parametersobject否内联内容中 {{ tokens }} 的值;序列化后最大 16 KB;所有收件人共享
tags{name, value}[]否最多 20 个;name ≤ 32 字符,value ≤ 64 字符;仅限 [A-Za-z0-9_-];每次发送名称唯一
metadataobject否任意 JSON,序列化后最大 2 KB
track_opensboolean否默认 true
track_clicksboolean否默认 true
categorystring否marketing 或 transactional;模板发送时默认使用模板的类别,否则为 marketing
ip_pool_idstring否ipp_... 或 ipp_shared;省略则使用组织的默认池
templateobject否通过 id 或 slug 发送已发布的模板,用 parameters 传递变量值,可选 language
attachmentsobject[]否最多 20 个;见附件
scheduled_atRFC 3339 时间戳否调度内联内容或 template;参见定时发送

使用模板发送

除了内联内容外,你也可以发送已发布的模板:将 template 设为一个对象,通过 id(emt_...)或 slug 指定模板(二者只选其一),并在 template.parameters 中传入变量值。省略 subject、html 和 text,因为模板中已包含这些内容。
const msg = await bird.email.send({
  from: { email: "onboarding@messagebird.dev", name: "Bird" },
  to: ["delivered@messagebird.dev"],
  category: "transactional",
  template: {
    slug: "welcome-email",
    parameters: { first_name: "Jane" },
  },
});
console.log(msg.id, msg.status);
模板的内容是 Liquid,因此除了简单的 {{ variable }} 替换外,还可以使用过滤器、{% if %} 条件和 {% for %} 循环。使用变量个性化列出了发布时会拒绝的少数语法结构。template.parameters 用于传入模板自身参数的值,以名称为键。缺少某个值时,发送会以 422 被拒绝并指出参数名。发送的其他方面与内联发送行为一致,包括收件人、tags、metadata、跟踪和附件。模板发送的特殊之处:
  • 内联或模板,不可同时使用。 将 template 与 subject、html 或 text 一起发送会被以 422 拒绝。API 同样会拒绝在顶层 parameters 字段中传入变量值;模板发送时它们应放在 template.parameters 中。
  • bird 是唯一的保留名称。 以 bird. 开头的占位路径引用的是我们自己的数据,例如退订链接或收件人的联系人记录,因此 template.parameters 键不能命名为 bird。其他键均可自定义,且每个键是单个扁平单词:{"order_number": "A-1043"} 填充 {{ order_number }}。
  • 模板可以立即发送,也可以稍后发送。 添加 scheduled_at 以定时发送。我们在接受时锁定已发布的版本、所选语言和参数值。如果您在发送时间之前删除了模板,消息会被拒绝并返回 generation_failure。
  • 发送使用模板的已发布版本。 草稿不会被发送。未知模板会被以 404 拒绝,没有已发布版本的模板则被以 422 拒绝。
  • language 选择模板的其中一种语言。 省略此项则发送模板的默认语言。请求一种模板不支持的语言时,模板自身的 on_missing_language 设置决定是发送最接近的匹配还是拒绝发送。设置了 language_source_required 的模板会拒绝未指定语言的发送。
  • 模板的类别是默认值,你的设置会覆盖它。 省略 category,发送会继承模板的类别,因此事务性模板无需在每次调用时重复指定。
邮件模板介绍了模板的编写、发布以及模板可包含的语法结构。

标签与元数据

两者都可以将你的自定义数据附加到发送中,区别在于后续的查询方式:
  • tags 是结构化的 {name, value} 键值对:每次发送最多 20 个,名称最长 32 个字符,值最长 64 个字符,仅限 ASCII 字母、数字、下划线和连字符,且名称在发送内唯一。标签是筛选维度,你可以按标签筛选消息列表,也可以在分析和仪表板汇总中按标签切分数据。适用于低基数标签,例如 campaign、experiment_variant 或 source。
  • metadata 是任意 JSON 对象,序列化后最大 2 KB。我们会存储它,在 API 读取时返回,并在每个 webhook 事件中回传,因此适合你希望回传给自己的上下文信息:内部 ID、外键、结构化载荷。
每个 webhook 事件同时包含两者以及关联 ID(email_id、recipient_id),因此你无需二次查询即可与自己的记录进行对账。以 __bird 开头的标签名称和顶层元数据键会被拒绝。你不需要将设备、地理位置、邮箱服务商、退信类型或收件人域名编码到任何一个字段中,因为我们已经将它们作为分析维度进行了捕获。
代码示例
{
  "tags": [{ "name": "campaign", "value": "onboarding" }],
  "metadata": { "user_id": "usr_12345", "order_id": "ord_98765" }
}

附件

attachments 每条消息最多接受 20 个文件,以 base64 编码的字节内联传入。我们会拒绝 base64 编码后生成消息估计大小超过 20 MB 的发送,因此原始附件内容应控制在 15 MB 以内以留出余量。附件包含字段约定、内联图片、被阻止的文件类型以及如何下载附件。

202 的含义

成功的发送返回 202 Accepted,其中包含一个以 em_ 为前缀的消息 ID 和 status: accepted:
代码示例
{
  "id": "em_01ky7ma8y2es1s2akzk53tmjn0",
  "status": "accepted",
  "category": "marketing",
  "from": { "email": "hello@yourdomain.com" },
  "to": [{ "email": "delivered@messagebird.dev" }],
  "subject": "Hello from Bird",
  "accepted_count": 1,
  "processed_count": 0,
  "delivered_count": 0,
  "deferred_count": 0,
  "bounced_count": 0,
  "complained_count": 0,
  "rejected_count": 0,
  "open_count": 0,
  "click_count": 0,
  "track_opens": true,
  "track_clicks": true,
  "created_at": "2026-07-23T13:58:20.866Z"
}
202 表示我们已持久化接受了该发送。你可以修复的失败会在请求本身以 422 返回:例如未验证的发件人域名或未通过校验的字段。每个收件人的投递结果(已送达、已退信、已延迟、已投诉)会在之后通过 webhook 和消息读取端点送达。
由此产生两个结论:
  • 读取返回状态,不含正文。GET /v1/email/messages/{message_id} 返回消息和收件人状态,不返回 html 或 text 正文。当工作区启用了内容存储时,存储的正文可在 GET /v1/email/messages/{message_id}/content 中保留最多 30 天。
  • 读取可能短暂滞后于发送。 在 202 之后立即在读取端点收到 404,表示消息尚未可见,请稍后重试。

安全重试

发送时附带 Idempotency-Key 请求头,每次逻辑发送使用唯一值。如果请求已成功但你未收到响应,使用相同的键重放请求。API 会返回原始结果而非再发一封邮件,并附带 Idempotency-Replay 请求头。幂等性包含密钥格式和保留策略。

批量发送

为减少 API 请求,POST /v1/email/batches 接受最多 100 条独立消息并作为一个整体进行校验。也支持在循环中调用单发端点。批量中的每个项使用本页的载荷格式(含 scheduled_at),因此一个批量可以混合即时和定时消息。

计费

邮件发送按收件人计费,从你的计划月度配额中扣除,因此发送给三个收件人的一封邮件消耗三次发送额度。计费与用量介绍了计量模型和实时用量查询。

后续步骤

  • 邮件模板:编写和发布你在此处发送的模板
  • 类别:marketing 和 transactional 如何改变屏蔽行为
  • 屏蔽:我们不投递给谁,以及原因
  • 定时发送:使用 scheduled_at 在未来时间投递
  • 测试沙盒:沙盒收件人和验证前发送
  • API 参考文档:完整的请求和响应 schema