API 参考
POST /api/v1/send 是整个事务性发送 API——一个经过版本化、稳定的端点。与 MailInApp 其他路由(那些路由只有我们自己的前端会调用,可以随意更改)不同,这一个端点是第三方代码所依赖的契约,因此从第一天起就进行了版本化。
身份验证
Authorization: Bearer mia_live_...
缺失或无效的密钥会返回 401。密钥在仪表盘的开发者下管理——参见快速入门。已撤销的密钥会立即失效。
请求
Content-Type: application/json。该端点共享两种请求形态——具体采用哪一种,取决于请求中是否存在 projectId。
自由格式
| 字段 | 类型 | 是否必需 | 说明 |
| --- | --- | --- | --- |
| to | string | 是 | 单个收件人地址。 |
| subject | string | 是 | 会被截断至 200 个字符。 |
| html | string | 是 | 原样发送——不进行渲染,不解析合并标签,不经过工作室渲染管道。 |
| text | string | 是 | 纯文本部分。 |
模板
| 字段 | 类型 | 是否必需 | 说明 |
| --- | --- | --- | --- |
| to | string | 是 | 单个收件人地址。 |
| projectId | string | 是 | 必须是你拥有的项目——工作室 URL 为 /studio/<projectId>。 |
| mergeData | object | 否 | 一个扁平的 { key: value } 对象,最多 100 个字段。用来代替一行数据源数据——会解析进项目的 {{field}} 合并标签中。嵌套对象/数组会被丢弃;null/undefined 会变为空字符串;其他值都会被转换为字符串。 |
| subject | string | 否 | 省略时默认使用项目名称。主题中的合并标签会从 mergeData 中解析。会被截断至 200 个字符。 |
请求体如果两种形态都不匹配(例如缺少 to,或同时缺少 html/text 与 projectId),会返回 400。
通用字段
| 字段 | 类型 | 是否必需 | 说明 |
| --- | --- | --- | --- |
| type | "transactional" | "marketing" | 否 | 默认为 "transactional"。参见下文。 |
| from | object | 否 | 针对某次调用覆盖发件地址/名称——{ "email": string, "name"?: string }。参见下文。 |
| senderId | string | 否 | 通过选择你账户已保存的发件人身份之一,针对某次调用进行覆盖,而不必内联拼写 from。必须属于你的账户,否则返回 400。如果两者都提供,from 优先。 |
| replyTo | string | 否 | 针对某次调用的 Reply-To 地址。参见下文。 |
请求头
| 请求头 | 是否必需 | 说明 |
| --- | --- | --- |
| Authorization | 是 | Bearer <apiKey>。 |
| Idempotency-Key | 否 | 参见幂等性。 |
响应
{ "id": "abc123", "status": "sent" }
status 取值为以下之一:
| 状态 | 含义 |
| --- | --- |
| sent | 已成功交给你的发送方式(SMTP 中继或原生发送)处理。 |
| suppressed | 该收件人在你的屏蔽列表中——参见事务性与营销性对比。没有发出邮件;这不算错误。 |
| failed | 发送尝试失败(例如你的 SMTP 中继未配置,或拒绝了这封邮件)。error 字段会携带一段人类可读的原因说明。 |
错误码
| 状态 | 含义 |
| --- | --- |
| 400 | JSON 格式错误,请求两种形态都不匹配,type 无效,from 无效或不被允许(参见发件地址),replyTo 无效,或(模板模式下)projectId 不存在或不属于你。当账户没有配置可用的发送方式时也会返回此错误。 |
| 401 | 缺失或无效的 Authorization 请求头,或该密钥已被撤销。 |
| 404 | (模板模式)该项目不存在,或不属于你的账户——刻意与"不存在"返回相同的响应,以避免向其他账户泄露哪些项目 ID 是有效的。 |
| 429 | 超出速率限制——参见速率限制。 |
当请求本身有效,但实际发送尝试在下游失败时,会返回 200 并附带 status: "failed"(而不是非 2xx 状态码)。如果你需要区分"我们拒绝了你的请求"与"我们尝试发送但没有成功",请检查响应体中的 status,而不仅仅是 HTTP 状态码。
事务性与营销性对比
type 字段决定检查哪一份屏蔽列表,与 ESP 区分事务性和营销性发送流的方式相同:
type: "transactional"(默认)——绕过取消订阅屏蔽。密码重置或订单收据不应仅因为收件人取消订阅了你的通讯简报就被拦截。它绝不会绕过退信屏蔽——一个失效的地址无论意图如何都仍然是失效的。type: "marketing"——行为与仪表盘中的一次营销活动发送完全相同:同时受取消订阅屏蔽和退信屏蔽的限制。
无论哪种情况,被拦截时都会返回 status: "suppressed",而不是一个错误。
发件地址
默认情况下,每次发送都使用你账户已配置的发件人身份——如果你使用原生(SES)发送,则是设置 → 域名中的"发件地址"卡片,否则就是你 SMTP 中继配置的发件地址。传入 from 可以为某一次调用覆盖它:
{
"to": "[email protected]",
"subject": "Your one-time code",
"html": "<p>Your code is 123456</p>",
"text": "Your code is 123456",
"from": { "email": "[email protected]", "name": "FitConsent Sales Team" }
}
只要出现了 from,就必须提供 from.email——不支持只提供名称的覆盖方式,因此这里的值一旦提供,就会为该次调用完全替换地址和显示名称。from.name 是可选的;省略它则只使用裸地址发送。
如果你的账户通过已验证域名发送(原生/SES 发送),from.email 必须是你自己某个已验证发送域名下的地址(例如 [email protected],而不是 [email protected])——一个账户可以验证多个域名,因此其中任意一个都可以使用,但绝不能是别人的域名。原生发送运行在一个由所有 MailInApp 客户共享的平台级 AWS 账户上,因此这项限制正是用来防止一个账户发出的邮件看起来像是来自另一个账户已验证的域名。在 SMTP 发送下则没有这项限制:邮件通过你自己的中继/凭据发出,已经受到你自己中继本身的发件人验证规则的信任约束。
senderId(见上方通用字段表)通常是更简单的选择,前提是你已经在仪表盘中设置好一个发件人身份。它会解析为该身份自己的 {email, name, reply-to},你无需在每次调用中重复填写。
无效或不被允许的 from 会在任何发送尝试之前返回 400。
回复地址
默认情况下,回复会发送到你账户配置的默认回复地址,该地址根据你的发送方式,在设置下的发送或域名页面中设置;如果你没有设置,则不会有默认回复地址。传入 replyTo 可以为某一次调用覆盖它——当可见的 from 是一个免回复地址,但你仍希望有人看到回复时非常有用:
{
"to": "[email protected]",
"subject": "Your order shipped",
"html": "<p>Your order is on its way.</p>",
"text": "Your order is on its way.",
"from": { "email": "[email protected]", "name": "FitConsent" },
"replyTo": "[email protected]"
}
与 from.email 不同,replyTo 在原生(SES)发送下没有域名限制——它从不影响发件身份或送达信誉,只是收件人邮件客户端在点击回复时所遵循的一个请求头。无效的 replyTo 会在任何发送尝试之前返回 400。
幂等性
在任何可能被重试的调用上传入 Idempotency-Key 请求头——例如结账 Webhook 的重新投递,或队列消费者对同一条消息的重新处理。针对同一账户、使用相同键重试的调用,会返回第一次调用的 {id, status},而不会再发送第二封邮件,即使第一次调用仍在处理中也是如此。
密钥的作用范围是每个账户,且没有过期时间;对不同的负载重复使用同一个已用过的键是你自己需要避免的事情(它仍然会返回第一次调用的结果,而不会发送新的负载)。如果你不传入键,每次调用都会真实发送。
速率限制
三项独立的上限同时适用:
- **每个 API 密钥:**每分钟请求数上限。超出限制会针对该密钥返回
429——账户下的其他密钥不受影响。 - **每个账户,发送 API 配额:**你的套餐包含每个日历月可用的发送 API 调用次数(免费版为每月 500 次)。超出限额会返回
429,直到配额在每月 1 日重置。 - **每个账户,发送量:**你套餐的累计月度发送量上限,在所有发送路径之间共享(仪表盘发送、定时发送、生命周期邮件,以及本 API)。这与保护你账户其他发送行为的上限是同一个上限——发送 API 不会有单独的额度。
模板模式细节
模板模式的发送会像一次普通的收件人发送那样渲染所关联的项目:降级引擎的三个层级、互动模块,以及一个由 mergeData(而不是存储的联系人行)构建出的、经过签名的专属实时预览链接。下游的一切行为都与工作室驱动的发送完全相同:
- 投票、评分、表单提交和打开事件都会被记录,并显示在项目的响应视图中,归因到这一次具体的 API 调用,而不是某一行联系人数据。
- 你的项目 Webhook 会针对每一个互动事件触发,与任何其他收件人相同。
- 近期调用(状态、收件人、时间戳)会作为审计记录列在开发者仪表盘页面上。
与仪表盘发送唯一不同的一点是:该互动背后没有存储的数据源行,因此你自己这一侧的关联查询应该基于响应中的收件人标识符,而不是行索引。