邮件模板
模板是一个主题行和一个邮件正文,保存一次即可多次发送。你将需要变化的部分写成 {{ variable }} 占位符,发布模板,然后通过 slug 发送,而不必在每次 API 调用中粘贴相同的 HTML。模板属于你的工作区。
在 Email > Templates 中创建和管理模板,也可以通过 /v1/email/templates、bird CLI 或 MCP server 操作。类型化方法在 TypeScript、Python、PHP 和 Go SDK 的 email.templates 下提供。完整的请求和响应模式见 API 参考文档。通过常规的发送端点发送已发布的模板。
模板的组成
每个模板有两个名称,用途各不相同:
- slug 是你用来发送模板的名称,例如 welcome-email。你在创建模板时选定它,之后无法更改。Slug 可以包含小写字母、数字、连字符和下划线,必须以字母或数字开头和结尾,长度最多 63 个字符。有两个前缀不可使用:bird_,它保留给我们的内置模板;emt_,它是模板 ID 使用的格式。控制面板中该字段叫做 Alias。
- name 是自由文本的显示标签。默认值为 slug,你可以随时更改。没有任何内容通过 name 解析,因此为了显示而重命名模板不会影响发送。
除此之外,模板还有一个永久的 emt_ ID,在其整个生命周期内固定不变。它还有一个 category,取值为 marketing 或 transactional,以及一个创作 source:html,即你提供的完成标记,可选择使用 Liquid 进行个性化。类别和来源在创建模板时固定。
我们提供一组内置模板,它们的 slug 都以 bird_ 开头。内置模板不属于任何工作区,不可编辑,始终可以直接发送。将其复制到你的工作区后它就变成你自己的模板,可以自由编辑。复制后的模板以未发布草稿的形式出现,继承了原始模板的类别、来源和语言设置,因此在发送前需要先发布。
草稿和已发布版本
每个模板恰好有一个草稿,即你编辑的工作副本。它还可以有任意数量的已发布版本,每个版本都有编号(1、2、3,依此类推),一旦生成就不再更改。编辑直接修改草稿。发布会对当前草稿做一次快照,将其变为下一个编号版本,并使该版本成为发送时使用的版本。草稿本身仍然可编辑,你可以继续准备下一次更改。
对于发送而言,关键规则是:发送始终使用模板的已发布版本,草稿永远不会被直接发送。你可以在一个稳定版本持续发出的同时继续编辑草稿,等更改准备好后再发布。发布新版本会改变后续发送渲染的内容。已被接受的发送不受后续发布的影响。

版本还支持另外两个操作。丢弃草稿的更改可以将草稿重置为当前已发布的内容。或者回滚,使一个更早的已发布版本重新成为发送使用的版本。你只能回滚到已发布的版本,不能回滚到草稿本身。回滚会用该版本的内容替换草稿,因此草稿中未保存的内容会丢失,后续编辑将从你回滚到的版本开始。回滚不会创建新版本。
要选择已有图片,你需要工作区媒体库的读取权限。要上传、粘贴或拖放新图片,你需要写入权限。如果 插入图片 已停用,或你无法浏览媒体库或上传图片,请向工作区管理员申请相应的媒体库权限。仅有模板编辑权限并不代表拥有媒体库访问权限。
在控制台中,选择 可视化 > 插入图片,搜索媒体库或上传不超过 5 MB 的 PNG、JPEG、GIF 或 WebP 图片。静态 WebP 图片会转换为 PNG 或 JPEG。选中图片后,可设置 图片描述、显示宽度、对齐方式和链接。只有图片不传达任何信息时,才将其标记为 装饰性图片;带链接的图片需要用描述说明链接目标。每种语言都保留各自的图片描述和布局。
使用 替换图片 可更换选中的图片,同时保留其描述、链接、宽度和对齐方式。你也可以向可视化编辑器粘贴或拖放图片文件,每次一个。保存或发送测试邮件之前,请等待上传完成或取消上传。检查预览后,打开 更多操作 > 测试邮件,将当前内容发送给自己。你仍可使用 代码 模式编辑 HTML。
从媒体库删除图片不会将其从已发送的邮件中移除。替换图片会使用新的 URL,因此之前的邮件仍会显示原图。
保存受修订号保护。保存时发送你最后读取到的该语言的 revision。如果其他人在此期间修改了该语言,保存会作为冲突被拒绝,而不是覆盖他们的工作。省略 revision 可以无条件保存。发布和回滚以相同方式使用草稿自身的 revision。
多语言内容
模板最多可容纳 25 种语言的内容,每种语言有自己的主题和正文,使用 BCP-47 代码标记,例如 en 或 pt-BR。其中一种语言是模板的默认语言。发布模板时会同时发布它包含的所有语言,不能单独发布某一种语言,因此所有语言都必须先完成。每种语言都需要主题和正文,并且模板的默认语言必须是你已填写内容的语言之一。如果有任何缺失,则不会发布任何内容,错误信息会告诉你每种语言缺少什么,以便你一次性修复。你不必一开始就完成所有语言:先发布准备好的,其余稍后再添加。
每种语言需要一个 HTML 正文。你可以省略其 text:发布时会自动从 HTML 生成纯文本替代版本,这样无需手动编写第二份内容即可同时拥有两种格式。
每种语言还可以包含预览文本(有时称为 preheader):收件箱在消息列表中主题之后显示的那行文字。它是可选的,最多 255 个字符,使用与主题相同的 {{ variable }} 占位符。如果省略,收件箱会回退到正文开头的第一行,而那通常不是你想展示的内容。如果某种语言的正文没有 HTML 部分,发布时会拒绝其预览文本,因为邮件客户端只从隐藏的 HTML 标记中读取预览行;同样会拒绝其中的 {{ bird.unsubscribe_url }},原因与主题不能包含链接一样:两者都不是放置链接的位置。
有两个设置用于处理发送时指定了模板中没有的语言的情况,它们各自防范不同的错误:
| 设置 | 控制内容 |
|---|---|
| on_missing_language | 当发送请求的语言模板中没有时的行为。fallback 为默认值,提供最接近的匹配。它先尝试同一语言的更宽泛形式,例如已存储的 pt 可以服务于对 pt-BR 的请求,然后回退到模板的默认语言。fail 则直接拒绝发送,适用于发送错误语言比不发送更严重的场景。 |
| language_source_required | 发送时是否必须指定语言。此设置默认关闭,因此未指定语言的发送会使用默认语言。开启后,未指定语言的发送会被拒绝。群发为整个受众指定一种语言,因此当模板开启了此设置时,群发前需要先选好语言。 |
你可以独立设置这两项。单独启用 fail 时,它仅在发送指定了我们没有的语言时生效,因此未指定语言的发送仍然可以通过。同时开启两项设置,可以要求每次发送都必须明确指定语言。
使用变量进行个性化
在主题、预览文本和正文中编写 {{ variable }} 占位符。我们会自动提取它们,并跨所有语言合并,因此无需单独声明。占位符前缀区分两种类型。以 bird. 开头的路径从我们的数据中读取,可以是联系人记录或退订链接。其他所有路径都是参数,需要你在发送时提供值。
参数名称是一个单词,例如 {{ animal }}。带点号的名称试图访问参数不具备的结构,因此发布时会被拒绝:请将该值写成独立的参数,或使用 bird.contact.<attribute> 读取联系人数据。
在单次发送或批量发送中,参数的值来自发送的 template.parameters 对象,以参数名称为键。一组值覆盖该次发送的所有收件人。bird 是你不能在其中使用的名称:template.parameters 中名为 bird 的键会被拒绝,并返回 422。
广播没有 parameters 对象,因此其内容只能使用 bird. 占位符。bird.contact.<attribute> 从每个收件人自己的联系人属性中填充,这就是按收件人个性化内容的方式。每个联系人属性都可通过其键访问,三个内置字段也是如此:first_name、last_name 和 email。
代码示例
Hi {{ bird.contact.first_name }},模板中的每个参数在发送时都需要一个值。否则,API 会返回一个 422,其中指出缺少的参数。请为所有语言的参数都提供值,因为所选语言可能取决于回退设置。缺少的联系人属性会渲染为空值,因此请为面向客户的内容添加回退值:{{ bird.contact.first_name | default: "there" }}。
广播对它接受的名称更严格,因为联系人属性是它唯一可以用来填充占位符的数据。它的 bird.contact.* 占位符只能引用内置字段或工作区已注册的联系人属性。任何其他占位符(包括参数)都是广播无法填充的。发送会被拒绝,错误信息会指出该占位符。
归档一个属性对模板的影响仅限于新内容:该属性会从编辑器的选择器中消失,发布内容中引用了该属性的版本也会被拒绝,错误信息会指出该属性。归档之前已发布的版本不受影响。
占位符使用 Liquid,因此过滤器和控制流可以与简单替换一起使用。{% if %} 条件判断和对数组值的 {% for %} 循环都是允许的。有少数语法结构在发布时会被拒绝,错误信息会准确指出需要更改的内容:
- 使用 {% include %} 或 {% render %} 的局部包含。
- increment、decrement 和 ifchanged 标签。
- money、format_date、format_time、json、inspect 和 type 过滤器。
- 与 empty 或 blank 进行比较。请改用 .size == 0。
- 嵌套深度远超实际邮件标记所需的块。
广播的模板完全不能使用 {% for %} 循环,因为广播为每个联系人属性只填入一个值,没有可迭代的内容。如果你的内容需要循环,请通过 messages API 发送。
每个模板都使用 Liquid,即使只包含 {{ variable }} 占位符也是如此。发布前,我们会将主题、预览文本、HTML 和纯文本内容作为 Liquid 进行验证。我们还会为每个尚未以 escape 或 escape_once 结尾的 HTML 输出添加 escape 过滤器,这样包含 & 或 < 的值就无法篡改周围的标记。预留的退订输出保持不变,以便发送时替换。主题行和纯文本正文保持原样。由于发布会添加这些过滤器,你从已发布版本读取的 HTML 可能与你提交的内容不完全一致。
将完整的 URL 直接放在 href 中,例如 <a href="{{ sign_in_url }}">Sign in</a>。不要对整个值添加 url_encode。它会对 https://、/、? 和 & 进行百分号编码,导致结果无法作为绝对链接使用。我们会在保留 URL 结构的同时添加 HTML 转义。当参数只提供 URL 的一个组成部分时,请显式编码该组成部分:<a href="https://example.com/search?q={{ query | url_encode }}">Search</a>。
发布前预览
使用示例值渲染模板,获取发送时实际投递的主题行、HTML 正文和纯文本正文。预览使用我们的本地 Liquid 渲染器,默认渲染草稿,方便你在上线前检查更改。也可以改为渲染已发布的版本。它适用于你自己的模板和我们的内置模板,不会实际发送任何内容。
你也可以自行传入内容,而不是让它读取草稿。传入主题和正文,它们会像草稿一样被渲染,这使编辑器可以在输入时实时展示更改,无需先保存。
个性化内容会自动填充,因此返回的结果是完成的正文,而不是 {{ }} 占位符。指定一个 contact,每个 bird.contact.<attribute> 都会根据该联系人自己的属性进行解析,方便你在任何人收到邮件之前用真实记录检查措辞。这些值来自广播填充占位符时使用的同一映射,因此预览的结果与发送的结果一致。
省略 contact 则会替换为占位值:Bird 和 Test 分别代表名字和姓氏,bird.test@example.com 代表邮箱,其他属性则使用各自注册的默认值。一个没有默认值的被引用属性会以方括号包裹其键名的形式渲染,例如 [loyalty_tier],这既表明该值是占位符,也指出哪个属性还需要设置默认值。
联系人数据读取的是当前状态。因此预览适合检查即将发送的内容,不适合查看之前发送过的内容。要查看某次发送实际投递的内容,请在邮件日志中打开该消息,它会使用该次发送携带的值进行渲染。
添加 language 以渲染特定语言,或省略以使用模板的默认语言。响应会告诉你渲染了哪种语言,当你请求的语言不存在且模板的 on_missing_language 提供了近似匹配时,这一点尤为重要。
如果草稿中的个性化内容在发布时会被拒绝,预览会返回相同的错误,因此它也可以作为提前发现问题的方式。
在面板的模板构建器中,左侧栏底部的 Preview with contact data 会在你编辑时显示渲染后的邮件,可视化编辑器和代码编辑器均可使用。下方的选择器用于选择以谁的数据填充占位符,Sample data 即上述的占位值。
使用模板发送
将发送的 template 字段设为一个对象来指定模板,通过 id(emt_...)或 slug 来命名,两者只能选其一。将变量值放入 template.parameters。添加 language 以选择特定语言,或省略以发送模板的默认语言,除非模板要求每次发送都指定语言。完全省略 subject、html 和 text,因为模板已经提供了它们。
代码示例
{
"from": "hello@yourdomain.com",
"to": ["delivered@messagebird.dev"],
"category": "transactional",
"template": {
"slug": "welcome-email",
"parameters": { "first_name": "Jane" }
}
}有一个值得注意的行为:模板的类别是默认值,发送自身的 category 会覆盖它。不设置 category,发送就会继承模板的类别,因此运营类模板会以事务性邮件发送,无需每次调用都重复指定。设置了 category,则以你的值为准。发送端的其余约定请参阅使用模板发送。
在面板之外编写
整个生命周期都可以在面板之外操作。发布步骤在那里叫做 submit,它是将草稿转为下一个已发布版本的操作:
代码示例
bird email templates create welcome-email --category marketing --source html
bird email templates versions languages set <emt_...> <emv_...> en --subject "Hi {{ bird.contact.first_name }}" --html "<p>Hello</p>"
bird email templates versions submit <emt_...> <emv_...> --validate-only # report problems, freeze nothing
bird email templates versions submit <emt_...> <emv_...> # freeze, go livecreate 返回模板及其 draft_version_id,每个版本和语言命令都需要用到它。--validate-only 运行与实际提交相同的完整性检查,但不会冻结任何内容,因此它是在一次调用中发现所有语言中所有问题的低成本方式。读取模板会返回其元数据和各语言的状态,但不包含内容。内容存储在版本的语言中,一次一种语言。
SDK 在 email.templates 下以类型化方法提供相同的生命周期,版本和语言操作分别嵌套在 email.templates.versions 和 email.templates.versions.languages 下。代理通过 email_templates_* MCP 工具访问相同的操作。
后续步骤
- 发送邮件:完整的发送载荷,以及模板化发送如何融入其中
- 类别:在每次发送时选择 marketing 还是 transactional
- bird email templates:从终端管理模板
- API 参考:所有十八个模板操作的完整请求和响应结构
- SDK:TypeScript、Python、PHP 和 Go 中的类型化 email.templates 方法
- MCP server:让代理编写和发布模板
- 如何构建邮件模板:一段在面板中构建模板、然后让代理构建另一个模板的视频