API 能做什么
API 管理一个工作区的邮件服务。它能做的事,管理员在管理控制台里也都能做;有了 API,就可以交给脚本、集成或 Agent 去做。
- 域名:验证所有权、接入域名、检查 DNS、设置 catch-all、添加别名域名。
- 邮箱:创建、删除和恢复邮箱,修改密码、状态、容量、备注和每日发信上限。
- 地址:转发规则、普通别名和邮件列表。
- 合规与报表:法律保留、不活跃邮箱、各域名的存储用量和发信最多的邮箱。
- 事件:邮箱、域名、订单、批量任务和安全事件的签名 Webhook。
- 收发邮件:用你的域名发信、跟踪每封邮件的投递,并以 Webhook 接收邮件(需开通)。
- Agent 邮箱:一次调用为 AI Agent 建好邮箱,读收件箱、按会话回复,并发一把只能用于这个邮箱的密钥。
- 邮件应用:让邮箱用户通过 OAuth 授权你的应用读取他们的邮件、以他们的身份发信。
- 投递:抑制列表、邮件追踪,以及替你读回的延迟退信和垃圾邮件投诉。
- 客户端与连接器:TypeScript 和 Python SDK、命令行工具、n8n 和 Zapier,以及飞书、钉钉群机器人(见下方应用目录)。
- 批量:把多项修改放进一个按顺序执行、可安全重试的请求。
申请工作区和密钥
- 申请工作区。 API 属于管理控制台里的一个工作区。申请工作区后会有人工审核,通过后你会作为管理员收到邀请。如果申请页提供“开发者工作区”,选它即可在确认邮箱后立即获得一个,用测试密钥试用 API。
- 创建密钥。 在管理控制台打开 设置 → API 密钥,创建一个密钥,只勾选代码需要的权限范围。只有管理员能创建密钥。
- 立即保存。 完整的密钥只在创建时显示一次。把它放在密钥管理工具或环境变量里,不要写进代码仓库、网址或日志。
密钥能做的事永远不会超过其所有者当前在控制台里的角色;吊销后,下一次请求就会被拒绝。工作区的每位管理员都能看到并吊销其他账号的密钥。
第一个请求
所有请求都发往 https://console.abnormalhq.com/api/v1,并以 Bearer 令牌的形式带上密钥。建议第一次先调用 GET /capabilities:它会返回可用的权限范围、功能和当前生效的限制。
API_BASE='https://console.abnormalhq.com/api/v1'
printf 'API key: '; stty -echo; IFS= read -r API_KEY; stty echo; printf '\n'
curl --fail-with-body "$API_BASE/capabilities" \
-H "Authorization: Bearer $API_KEY"用隐藏输入读入密钥,它就不会留在 shell 历史里。用完后运行 unset API_KEY。
权限范围
每个操作需要一个权限范围,在下方的接口列表里标在操作旁边。写权限不包含对应的读权限。有两个例外:POST /batch 会逐一检查其中每项操作的权限范围,GET /batch/{requestId} 只接受提交该批次的那个密钥。
domains:read、domains:write:域名、接入申请、DNS 与连接状态、catch-all、别名域名。mailboxes:read、mailboxes:write:邮箱、回收站、发信上限和用量报表。forwarders:*、aliases:*、lists:*:转发规则、普通别名和邮件列表。holds:*、webhooks:*、suppressions:*、trace:read:法律保留、Webhook、抑制列表和邮件追踪。agent_tasks:*:跟进任务,即以某个邮箱身份回复邮件的 Agent。emails:*、inbound:*:API 发信与收信路由(需手动勾选,新密钥默认不含)。agent_mailboxes:*、mail_apps:*:Agent 邮箱,以及邮箱用户可以授权的应用(同样需手动勾选)。
域名、转发、别名、邮件列表、法律保留和 Webhook 即使只读也需要管理员角色;邮箱和跟进任务的权限范围运营人员也能用,其中的读权限查看者也能用。
请求、响应与错误
- 请求体是 JSON(
Content-Type: application/json),最大 16 KiB。 - 成功结果是
{ "data": … },列表另带pagination。创建成功返回201。 - 路径里的
{id}是 API 返回的记录 ID,不是邮箱地址或域名。 POST /batch按顺序执行 1 到 25 个操作,并且必须带Idempotency-Key:用同一个键重发同一批操作,会直接返回保存的结果,不会执行两次。
所有错误的格式都一样。请根据 code 判断,不要根据 message:后者是写给人看的,内容可能会变。
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Try again in 42 seconds.",
"details": { "retryAfter": 42, "limit": 300 }
}
}遇到超时、连接中断或 502 后,先读取当前状态,再决定是否重做写操作。返回 409 且错误码为 provisioning_uncertain 时,邮箱可能已经创建也可能没有:请用 details.mailboxId 查询,不要再创建一次。
频率限制
- 按密钥计数,REST 和 MCP 合并计算。默认每分钟 300 次;当前生效的限制由
GET /capabilities的limits.requestsPerMinute给出。 - 每个响应都带
RateLimit-Limit、RateLimit-Remaining、RateLimit-Reset和RateLimit-Policy。 - 超出限制时返回
429,并用Retry-After给出需要等待的秒数。被拒绝的请求什么都没做,等待之后重发是安全的。 - 同一地址发来太多无效密钥,该地址会被暂时封禁,有效密钥也会被拒绝。请修好发错密钥的客户端。
Webhook
Webhook 是一个 https 地址,工作区里发生事情时会向它发送 POST 请求,你就不用轮询。用 POST /webhooks 创建;签名密钥(whsec_…)只在这次响应里出现一次。每个工作区最多 10 个。
每次投递都在 X-Mailhq-Signature 头里签名,格式为 t=<milliseconds>,v1=<hex HMAC-SHA256>(毫秒时间戳和十六进制的 HMAC-SHA256),签名内容是时间戳、一个点和原始请求体。先校验签名,再信任请求体:
import { createHmac, timingSafeEqual } from "node:crypto";
/** rawBody: the body exactly as received; header: the X-Mailhq-Signature value. */
export function verifyMailhqWebhook(secret, rawBody, header, toleranceMs = 5 * 60_000) {
const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header ?? "");
if (!match) return false;
const timestamp = Number(match[1]);
if (Math.abs(Date.now() - timestamp) > toleranceMs) return false;
const expected = createHmac("sha256", secret).update(`${timestamp}.`).update(rawBody).digest();
const given = Buffer.from(match[2], "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
}- 在 10 秒内返回
2xx,之后再处理业务。 - 投递至少一次且不保证顺序:用事件的
id去重。 - 投递失败会以逐渐拉长的间隔重试约 17 小时。连续失败 3 天的 Webhook 会被停用,直到你重新启用。
POST /webhooks/{id}/test会发送一个测试事件,用来检查接收端。设置 → 开发者 会列出所有 Webhook 最近的投递及接收方的回应,并可重新投递;状态页会显示 Webhook 投递是否准时。- 想不写接收端就在群聊里收到事件,创建 Webhook 时设
"format": "feishu"或"dingtalk",填写群机器人的地址(签名密钥填在botSecret):每个事件会以一条简短消息到达。
收发邮件
用你自己的域名、通过域名自己的邮件服务器发信,跟踪每封邮件的投递结果,并把指定邮箱收到的邮件推送到 Webhook。发信功能默认关闭,需由我们为你的工作区开通并设定每日上限,请来信申请。测试密钥可以直接向沙箱发信。
PUT /domains/{id}/sending为域名创建发信账号:邮件以该域名的 DKIM 密钥签名发出,退信回到这个账号,From 可以是该域名下的任意地址。POST /emails发送一封邮件(POST /emails/batch一次最多 50 封);带上Idempotency-Key即可安全重试。抑制列表中的收件人会被跳过。GET /emails/{id}给出每个收件人的结果:已发出、已送达、投递延迟或退信。同样的变化也会以email.sent、email.delivered、email.delivery_delayed、email.bounced和email.failed事件推送。PUT /mailboxes/{id}/inbound开启收信路由:邮箱收到的每封邮件都会以message.received推送,附带纯文本正文;完整邮件和附件用GET /mailboxes/{id}/messages/{emailId}读取。用别名或 catch-all 可以把多个地址汇入同一个路由邮箱。- 如果你的套餐按发信量计费,需先在 设置 → 开发者 → 计费 中订阅,否则正式密钥发信会得到
402 billing_required。每封交给邮件服务器的邮件计一次,与收件人数量无关。 - 管理后台的 设置 → 开发者 会显示当天的发信额度、最近的 API 调用及其错误码、每封发出的邮件和每个收件人的结果。
- 回到域名发信账号的延迟退信和垃圾邮件投诉(反馈环报告)会被读回:收件人变为
bounced,或得到complainedAt和一个email.complained事件,并被加入抑制列表。 - 新开通的工作区会先预热:第一天最多 50 封,此后每天翻倍,直到上限。退信或投诉过多时正式发信会自动暂停(
403 email_api_paused,并推送email.sending_paused事件),直到我们核查。 - 只能走 SMTP 的软件可以用
POST /domains/{id}/smtp-credentials经域名自己的服务器发信:这个密码只能登录和发信,与 API 密钥分开创建和撤销。这样发出的邮件不是 API 邮件:没有GET /emails记录,也没有投递事件,计入的是发信账户在服务器上自己的每日限额,而不是工作区的发信额度。
Agent 邮箱
Agent 邮箱是你域名下一个真正的邮箱,一次调用就为 AI Agent 建好,收信路由和发信都已开启。
POST /agent-mailboxes传入domain和name即可创建;GET /agent-mailboxes/{id}/messages读取收件箱,POST …/send发送新邮件,POST …/messages/{emailId}/reply在原会话中回复。POST /agent-mailboxes/{id}/keys返回一把只对这个邮箱有效的密钥:交给 Agent,它就碰不到工作区里的其他任何东西。- Agent 收到的邮件同时以
message.received事件推送,无需轮询。PATCH和DELETE …/messages/{emailId}可把邮件标为已读或删除,GET …/attachments/{index}可下载附件。
由邮箱用户授权的应用
你的应用可以在邮箱用户同意后读取其邮件、以其身份发信,全程看不到用户的密码:在 Abnormal 网页邮箱上使用 OAuth 2.0 授权码流程加 PKCE。
- 用
POST /mail-apps注册应用:名称、回调地址,以及它可以申请的权限mail.read、mail.send和mail.modify。所有应用都是公开客户端:没有 client secret,必须使用 PKCE(S256)。 - 把用户引导到网页邮箱的
/oauth/authorize;用户登录后会看到你的应用名称和申请的权限,确认即可。在/api/oauth/token用授权码换取访问令牌(1 小时)和每次使用都会轮换的刷新令牌。 - 带着令牌调用 Mail API:
GET /api/mail/v1/messages和/messages/{id}读取、搜索邮件(…/attachments/{index}下载附件),POST /api/mail/v1/send以该邮箱发信,可带附件并在“已发送”中留存副本;有mail.modify时应用还能标记、移动和删除邮件。 - 用户随时可以在网页邮箱设置里撤销授权,修改邮箱密码也会终止全部授权;
DELETE /mail-apps/{id}会一次终止你的应用的全部授权。
给 AI Agent 用的 MCP
同样的操作也作为工具提供在 MCP 服务器 https://console.abnormalhq.com/api/mcp 上(Streamable HTTP),密钥、权限范围和频率限制都相同。只给 Agent 它需要的权限范围。以 Claude Code 为例:
claude mcp add --transport http abnormal https://console.abnormalhq.com/api/mcp \
--header "Authorization: Bearer $ABNORMAL_API_KEY"支持 OAuth 登录的客户端(例如 claude.ai、Claude Desktop 和 Claude Code 上的 Claude)只需要这个地址:它们会自行注册,由管理员在授权页上批准权限,令牌会自动续期。不加请求头,客户端就会打开登录页。
应用目录
除了自己调用 API,把其他软件接入 Abnormal 的所有方式。管理控制台的 设置 → 开发者 → 集成 也列出了同样的内容。
- Claude 及其他 MCP 客户端(可用):上文的 MCP 服务器,支持 OAuth 登录。
- 你自己的应用,面向你的邮箱用户(可用):使用 OAuth 的邮件应用,见上文。
- 飞书和钉钉(可用):把 Webhook 事件或你的控制台通知推送到群机器人。
- Slack 和 Telegram(可用):把你的控制台通知发到频道或会话。
- TypeScript SDK、Python SDK 和 `abnormal` 命令行工具(尚未发布):带类型的调用、重试、幂等键和 Webhook 校验;命令行工具将可用
brew install abnormalhq/tap/abnormal安装。 - n8n 和 Zapier(尚未发布):n8n 的节点和触发器,以及 Zapier 的触发器、动作和搜索。
版本与变更
- 版本写在路径里:
/api/v1。在同一版本内只做新增:新接口、新的可选字段、新的响应字段、新的枚举值和新的事件类型。你的代码应当能接受不认识的字段和值。 - 在
v1里删除或修改任何东西都会先公告,在规范里标为弃用,并在每个响应里带上Deprecation和Sunset头:代码会发送的内容至少提前 6 个月,只会读取的字段或值至少提前 3 个月。 - 下方接口列表所依据的 OpenAPI 3.1 规范可以下载 JSON 文件,供代码生成器和 API 工具使用。
- 服务状态页会显示管理 API 近 30 天的可用率(每分钟检测一次),以及故障和维护。你自己的监控可以轮询
GET https://console.abnormalhq.com/api/health,无需密钥。
测试模式
创建密钥时打开 测试密钥,就可以在不动到正式域名和邮箱的情况下开发集成。它的令牌以 mhq_test_ 开头,所有调用都发生在本工作区的沙箱里;沙箱的邮件服务器是模拟的,不会投递任何邮件,也不会查询 DNS。
- 域名必须以工作区的专属区域
<slug>.test结尾(例如shop.acme.test),GET /capabilities会在sandbox.domainZone里给出它。所有权验证无需 DNS 即可通过。 - 邮箱、别名、邮件列表、转发、法律保留、批量操作和抑制列表都和正式工作区一样可用,Webhook 也会真实投递。
- 发信无需开通:除本地部分以
bounce开头的地址会退信外,所有收件人都会立即“送达”;发到开启收信路由的沙箱邮箱的邮件会以message.received推送。 - 邮件追踪、跟进任务、传输安全、读取收到的邮件(包括 Agent 邮箱的)和邮件应用需要真实的邮件,测试密钥调用它们会得到
403 test_mode_unsupported。 - 每个沙箱最多 5 个域名、100 个邮箱。
暂不提供
- 未经邮箱用户授权读取未开启路由的邮箱。
- 打开与点击跟踪:这是有意不做的,它会读取人们怎么处理自己的邮件,我们的网页邮箱也不这样做。
- SDK 包、命令行工具以及 n8n、Zapier 的上架已经做好但尚未发布:发布后本页会给出链接。
邮箱仍然通过网页邮箱、IMAP 和 SMTP 收发自己的邮件。关于 API 的问题请发邮件到 support@abnormalhq.com。
错误码
每个错误都有一个稳定的 code。下表列出全部错误码,以及对应的状态码、含义、处理办法和能否原样重试。v1 内可能新增错误码,遇到不认识的请按 HTTP 状态码处理。说明为英文。
- 不能:请求不改就会一直以同样方式失败。
- 稍后:什么都没发生,等一会儿可能成功。有
Retry-After时按它等待。 - 先核对:结果未知或状态已变。先读取当前状态再决定,盲目重发可能把事情做两遍。
认证、请求格式与限制
| 错误码 | 状态码 | 含义 | 处理办法 | 重试 |
|---|---|---|---|---|
| body_ | 413 | The request body is over 16 KiB (8 MiB for POST /emails and /emails/batch). | Send less per request; use several requests or a batch. | 不能 |
| endpoint_ | 410 | The operation was retired after its announced sunset date. | Use the replacement in details.replacement; see the API changelog. | 不能 |
| field_ | 422 | The request sent a field, query parameter or enum value retired after its sunset date. | Drop it or use details.replacement; details.field names it. | 不能 |
| forbidden | 403 | The key lacks a scope the operation needs, or its owner's current role no longer allows it. | Create a key with the scope listed for the operation in the reference, owned by an account whose role allows it. | 不能 |
| invalid_ | 400 | An MCP tool was called with arguments that are not a JSON object of the tool's own fields. | Pass exactly the fields in the tool's input schema. | 不能 |
| invalid_ | 400 | An ID in the path or body is not in the format of a record ID. | Use the IDs the API returns. | 不能 |
| invalid_ | 400 | The body is not valid JSON, or not a JSON object. | Send a JSON object. | 不能 |
| invalid_ | 400, 422 | A query parameter is unknown, malformed or out of range. | Fix the parameter details names; the reference lists each operation's parameters and their ranges. | 不能 |
| method_ | 405 | The path exists but not with this HTTP method. | Use one of the methods in the Allow header. | 不能 |
| not_ | 404 | The record in the path does not exist in this workspace (or is in the recycle bin, for mailboxes). | Check the ID: paths take the record IDs the API returns, not addresses or domain names. | 不能 |
| rate_ | 429 | The key's (or the address's) rate limit is used up for this window; nothing was done. | Wait Retry-After seconds, then send the same request again. Use batches for bulk changes. | 稍后 |
| unauthorized | 401 | No Bearer key was sent, or the key is unknown, expired, revoked or no longer valid for its owner. | Send a working key in Authorization: Bearer. Repeated invalid keys get the sending address rate-limited. | 不能 |
| unknown_ | 400 | The body has a field the operation does not take. | Remove it; details names it. | 不能 |
| unsupported_ | 415 | A request body was sent without Content-Type: application/json. | Send JSON with that content type. | 不能 |
域名、接入与别名域名
| 错误码 | 状态码 | 含义 | 处理办法 | 重试 |
|---|---|---|---|---|
| alias_ | 409 | The alias domain changed while the request ran. | Read it again, then decide whether to repeat the request. | 先核对 |
| alias_ | 409 | The domain already has the most alias domains allowed. | Remove one before adding another. | 不能 |
| already_ | 409 | The domain, alias domain or address already exists (here, or claimed by another workspace). | Use the existing record, or choose another name. | 不能 |
| dns_ | 502 | The DNS lookup for the ownership check failed; nothing was changed. | Try again later. | 稍后 |
| domain_ | 409 | The domain's server binding is ambiguous or changed while the request ran. | Have an admin review the domain's binding, then try again. | 先核对 |
| domain_ | 409 | The domain's binding changed while the request ran. | Read the domain again, then decide whether to repeat the request. | 先核对 |
| domain_ | 409 | The domain still has alias domains. | Remove its alias domains first. | 不能 |
| domain_ | 409 | The domain still has local mailboxes, so its record cannot be removed. | Delete or move its mailboxes first. | 不能 |
| domain_ | 409 | The domain to import is not on the chosen server. | Create it on that server first, or onboard it with /domain-onboardings. | 不能 |
| domain_ | 409 | The domain's onboarding has not finished, or its state does not allow this step. | Read the onboarding's state and DNS status; never submit the remote provisioning twice. | 先核对 |
| domain_ | 403 | The domain cannot be imported without a verified ownership onboarding in this workspace. | Onboard it with /domain-onboardings instead. | 不能 |
| domain_ | 409 | The domain is not bound to a valid mail server, so nothing on it can be changed remotely. | Bind the domain to a server in the admin console. | 不能 |
| invalid_ | 400 | The domain name is missing or not a valid name. | Send a lowercase domain name such as example.com. | 不能 |
| invalid_ | 400 | The status change does not say whether to enable or disable the domain. | Send { "enabled": true } or { "enabled": false }. | 不能 |
| ownership_ | 409 | The ownership TXT record was not found with the expected value. | Publish the record the connection details show, wait for DNS, then verify again. | 稍后 |
| remote_ | 422 | Deleting a domain on the mail server is not offered. | Disable the domain, then remove only the local record; the server keeps the domain and its data. | 不能 |
| subscription_ | 404 | The mail server in subscriptionId does not exist or is not available to this workspace. | Use an ID from GET /servers. | 不能 |
| subscription_ | 400 | Several mail servers are configured and the request does not say which one to use. | Send subscriptionId (one of GET /servers). | 不能 |
| verification_ | 409 | The alias domain's ownership value expired before it was verified. | Remove the alias domain, add it again and publish the new value. | 不能 |
邮箱与发信上限
| 错误码 | 状态码 | 含义 | 处理办法 | 重试 |
|---|---|---|---|---|
| invalid_ | 400 | The daily sending limit is not an integer in range. | Send an integer from 0 to 1000000. | 不能 |
| invalid_ | 422 | The mailbox fields failed validation. | Fix the fields listed in details.fields. | 不能 |
| invalid_ | 400 | The bulk sending-limit request does not name 1–100 distinct mailbox IDs. | Send between 1 and 100 distinct mailbox record IDs. | 不能 |
| invalid_ | 400, 422 | The note (of a mailbox or a suppression) is too long or not text. | Send at most the documented number of characters. | 不能 |
| invalid_ | 400 | The password is not 8–128 characters without whitespace. | Send a password that meets the rule. | 不能 |
| invalid_ | 400 | The quota is not a whole number of MB in range. | Send an integer from 0 (no limit) to the documented maximum. | 不能 |
| invalid_ | 400 | The status is not one of active, disabled or pending. | Send one of those values. | 不能 |
| invalid_ | 400 | The username (the part before @) is not valid. | Use 1–64 lowercase letters, digits, ., _ or -, starting and ending with a letter or digit. | 不能 |
| provisioning_ | 409 | The mailbox write was sent to the server but its result is not confirmed; a pending record was kept. | Look the mailbox up with details.mailboxId and verify it; do not create it again. | 先核对 |
| sending_ | 422 | A sending limit cannot be set for this mailbox (not created on the server yet, or not supported). | Create the mailbox first, or leave the limit to the server's default. | 不能 |
| sending_ | 409 | The sending-limit write may or may not have been applied. | Read the limit back (GET); repeat only the same target value, never a different one, if it is not set. | 先核对 |
别名、邮件列表、转发与 catch-all
| 错误码 | 状态码 | 含义 | 处理办法 | 重试 |
|---|---|---|---|---|
| address_ | 409 | The address is already used by a mailbox, service mailbox, alias, list, forwarder or a mailbox in the recycle bin. | Choose another address. | 不能 |
| external_ | 422 | The list has members outside the workspace's domains, which this workspace does not allow. | Remove the outside addresses, or ask an admin to allow external members. | 不能 |
| forwarding_ | 409 | The rule forwards to itself or would close a loop with existing forwarding. | Choose a different destination. | 不能 |
| invalid_ | 400 | The alias fields are invalid. | Fix the username, target or description. | 不能 |
| invalid_ | 409 | The alias target is not an existing managed mailbox of the same domain. | Point the alias at a mailbox of its own domain. | 不能 |
| invalid_ | 400 | The catch-all setting is invalid. | Send a mailbox address of the domain, or null to reject mail to unknown addresses. | 不能 |
| invalid_ | 409 | The catch-all destination is not an existing managed mailbox of this domain. | Use one of the domain's own mailboxes. | 不能 |
| invalid_ | 400 | The forwarding rule's source or destination is invalid. | Send one source address of the domain and one destination address. | 不能 |
| invalid_ | 400 | The distribution list fields are invalid. | Fix the username, description or members. | 不能 |
| list_ | 409 | The domain already has the most distribution lists allowed. | Delete a list before creating another. | 不能 |
| readonly_ | 409 | This alias is a special one managed on the mail server (or a user's masked address) and cannot be changed here. | Leave it as it is. | 不能 |
| readonly_ | 409 | This rule is not managed here (a list, alias, catch-all or custom Sieve rule). | Change it where it is managed. | 不能 |
| readonly_ | 409 | This list is managed on the mail server and cannot be changed here. | Leave it as it is. | 不能 |
| unsupported | 422 | The domain's mail server does not support this kind of address management. | Use a Stalwart 1.0 or later server for the domain. | 不能 |
批量操作
| 错误码 | 状态码 | 含义 | 处理办法 | 重试 |
|---|---|---|---|---|
| batch_ | 424 | An item of a batch that was not run because an earlier item's outcome is unconfirmed (item error.code). | Verify the earlier item, then send this one again in a new batch. | 先核对 |
| idempotency_ | 409 | This idempotency key was already used, with this key, for a different batch (or follow-up task, or messages sent through /emails). | Use a new idempotency key for different content. | 不能 |
| idempotency_ | 409 | A batch with this idempotency key is still running. | Read its state with GET /batch/{requestId}; do not resend under another key. | 先核对 |
| idempotency_ | 409 | A batch with this idempotency key stopped with an unconfirmed write. | Verify the affected records, then send only what is confirmed missing, in a new batch with a new key. | 先核对 |
| invalid_ | 400 | The batch body is invalid: not 1–25 operations, duplicate item IDs, an unknown operation or bad input; for POST /emails/batch, emails is not a list of 1 to 50 messages. | Fix the batch; nothing in it was run or sent. | 不能 |
| invalid_ | 400 | The Idempotency-Key header is missing or not 8–128 letters, digits, - or _. | Send a valid key, unique per batch. | 不能 |
| request_ | 422 | The batch reached its request budget on the mail server and the remaining operations were stopped. | Check the results, then send the rest in smaller batches. | 先核对 |
Webhook
| 错误码 | 状态码 | 含义 | 处理办法 | 重试 |
|---|---|---|---|---|
| delivery_ | 409 | The delivery is still waiting for its own retry. | Only a delivered or dead-lettered delivery can be sent again; wait for it to settle. | 稍后 |
| invalid_ | 400 | The description is longer than 200 characters. | Shorten it. | 不能 |
| invalid_ | 400 | enabled is not a boolean. | Send true or false. | 不能 |
| invalid_ | 400 | The events list names an unknown event type. | Use the event types in the reference, or ["*"]. | 不能 |
| invalid_ | 400 | A webhook update changes nothing. | Send at least one of url, description, events and enabled. | 不能 |
| invalid_ | 400 | format is not standard, feishu or dingtalk, botSecret came without a bot format or is not one, or a PATCH tried to change either (they are set when the webhook is made). | Send one of the formats, and a bot's signing secret only with a bot format; to change the format, make a new webhook. | 不能 |
| invalid_ | 400 | The address is not https on the default port with a public host name, or, for a bot format, not that bot platform's webhook address. | Use a public https address without credentials, IP literal or internal name. | 不能 |
| webhook_ | 409 | The workspace already has the most webhooks allowed (10). | Delete one before creating another. | 不能 |
法律保留与抑制列表
| 错误码 | 状态码 | 含义 | 处理办法 | 重试 |
|---|---|---|---|---|
| hold_ | 409 | The legal hold was already released or has ended. | Nothing to release; place a new hold if needed. | 不能 |
| invalid_ | 422 | The suppression list address is not a valid email address. | Send one valid address. | 不能 |
| invalid_ | 400 | The hold's end date is not a future ISO 8601 time. | Send a future date, or none. | 不能 |
| invalid_ | 400 | The required reason is missing or too long. | Send a reason. | 不能 |
邮件追踪
| 错误码 | 状态码 | 含义 | 处理办法 | 重试 |
|---|---|---|---|---|
| missing_ | 400 | A trace search names none of sender, recipient, Message-ID or queue ID. | Give at least one of them. | 不能 |
| trace_ | 409 | No mail server whose logs can be read is configured. | Ask an admin to connect a mail server. | 不能 |
跟进任务
| 错误码 | 状态码 | 含义 | 处理办法 | 重试 |
|---|---|---|---|---|
| already_ | 409 | This email awaiting approval was already approved or rejected. | Read the task's approvals again. | 不能 |
| auto_ | 409 | An admin switched automatic sending off, so every email of a task needs approval. | Create the task with approval required, or ask an admin. | 不能 |
| invalid_ | 400, 422 | The follow-up task's fields are invalid. | Fix what the message describes. | 不能 |
| invalid_ | 409 | The task is not in a state that allows this (only a running task can be paused, a paused one resumed). | Read the task's state first. | 先核对 |
| mailbox_ | 409 | The task's mailbox cannot be used (disabled, no stored password, or its domain has no mail server). | Fix the mailbox, or use another one. | 不能 |
| task_ | 409 | The task has already ended. | Create a new task if more follow-up is needed. | 不能 |
收发邮件、Agent 邮箱与邮件应用
| 错误码 | 状态码 | 含义 | 处理办法 | 重试 |
|---|---|---|---|---|
| attachments_ | 413 | The attachments are over 5 MiB in all, decoded. | Send smaller files, or a link to them. | 不能 |
| billing_ | 402 | The workspace's plan bills sending through the API, and the workspace has no subscription that may send (none, canceled, unpaid). | An admin subscribes, or fixes the payment, under Settings → Developers → Billing. details.status is the subscription's Stripe status. | 不能 |
| domain_ | 409 | The domain does not send through the API, so it has no sending account to make SMTP relay credentials for. | Turn sending on for the domain (PUT /domains/{id}/sending) first. | 不能 |
| email_ | 403 | Sending through the API is not turned on for this workspace: a platform admin has not approved it. | Ask the platform's operator to approve it (with a daily limit). Test keys can send to the sandbox meanwhile. | 不能 |
| email_ | 403 | Live sending is paused for the workspace: in the last 7 days (or since it was last resumed) too many recipients bounced or complained. details.reason says which, details.pausedAt since when. | Find out why (a stale list, a missing unsubscribe, a stolen key) and fix it, then ask the platform's operator to resume sending. Test keys still send to the sandbox. | 不能 |
| email_ | 429 | The workspace's daily allowance of messages is used up (a batch counts all of its messages). | Send again after details.resetAt (midnight UTC), or ask for a higher limit. Nothing of the request was sent. | 稍后 |
| from_ | 422 | The From address is not on a domain of this workspace that sends through the API. | Turn sending on for the domain (PUT /domains/{id}/sending), or send from a domain that has it. details.index names the message in a batch. | 不能 |
| invalid_ | 400 | An agent mailbox's name is not a single line of at most 200 characters without quotes or angle brackets. | Fix it. | 不能 |
| invalid_ | 400 | An attachment is malformed: no filename, content that is not base64, a bad content type, or more than 10 attachments. | Fix the attachments. | 不能 |
| invalid_ | 400 | The message has neither text nor html, or one of them is too long. | Send text, html or both, within the documented sizes. | 不能 |
| invalid_ | 400 | from is not an address or Name <address>. | Fix it. | 不能 |
| invalid_ | 400 | headers holds a header that is not allowed (only X- headers, List-Unsubscribe and List-Unsubscribe-Post), repeats one, or has a multi-line value. | Fix the headers. | 不能 |
| invalid_ | 400 | A mail app needs a name, 1 to 5 redirect URIs (https on a public host, or http on the loopback) and scopes from mail.read, mail.send and mail.modify. | Fix the app. | 不能 |
| invalid_ | 400 | A change to an agent mailbox's message needs read and/or flagged, each true or false, and nothing else. | Send { "read": true }, { "flagged": false } or both. | 不能 |
| invalid_ | 400 | to, cc, bcc or replyTo holds something that is not an address, or the message has no recipient or more than 50. | Fix the lists; send to more people in several messages or a batch. | 不能 |
| invalid_ | 400 | An SMTP relay credential needs a name: one line of at most 100 characters. | Name what the credential is for (the application that will use it). | 不能 |
| invalid_ | 400 | subject is missing, empty, over 500 characters or not a single line. | Fix it. | 不能 |
| invalid_ | 400 | tags is not an object of up to 10 names with string values. | Fix the tags. | 不能 |
| key_ | 403 | A key restricted to one agent mailbox called something other than that mailbox's own paths. | Use the key only for /agent-mailboxes/{its id} and the paths under it (not /keys, not DELETE); use another key for the rest. | 不能 |
| mail_ | 409 | The workspace already has 20 mail apps that are not revoked. | Revoke one first (DELETE /mail-apps/{id}). | 不能 |
| mailbox_ | 409 | The mailbox cannot be read for inbound routing: it is not active, or has no stored password. | Use an active mailbox with a stored password (created through the console or the API). | 不能 |
| nothing_ | 422 | The message has no Reply-To or From address other than the agent's own. | Send a new message with POST /agent-mailboxes/{id}/send instead. | 不能 |
| smtp_ | 409 | The domain already has 10 SMTP relay credentials in use. | Revoke one you no longer need (DELETE /domains/{id}/smtp-credentials/{credentialId}). | 不能 |
测试模式
| 错误码 | 状态码 | 含义 | 处理办法 | 重试 |
|---|---|---|---|---|
| sandbox_ | 422 | A test key asked for a domain outside its sandbox's zone (<workspace slug>.test). | Use a name under the zone GET /capabilities reports in sandbox.domainZone. | 不能 |
| test_ | 403 | A test key called an endpoint that needs real mail (message trace, follow-up tasks, transport security). | Use a live key for it. GET /capabilities lists these paths under sandbox.unsupportedPaths. | 不能 |
邮件服务器与服务可用性
| 错误码 | 状态码 | 含义 | 处理办法 | 重试 |
|---|---|---|---|---|
| service_ | 503 | The service or its mail server configuration is unavailable. | Try again later; read the current state before repeating a write. | 先核对 |
| upstream_ | 502 | The mail server refused or failed the request. | Read the current state before repeating a write; try reads again later. | 先核对 |
接口列表
v1 的全部操作,按功能分组,并标出所需的权限范围。路径相对于 API 地址;请求和响应的结构见规范。接口说明为英文。
Capabilities
Boundaries of the current API.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | Get API capabilities | domains:read |
Domain onboarding
Provision a domain after verifying ownership, or look up / cancel pending requests.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | List available mail servers | domains:read |
GET/ | List domain onboarding requests | domains:read |
POST/ | Request verification and provisioning of a domain | domains:write |
GET/ | Get a domain onboarding request | domains:read |
DELETE/ | Cancel an unsubmitted domain request | domains:write |
POST/ | Verify TXT and provision the domain | domains:write |
POST/ | Regenerate the ownership verification record | domains:write |
Domains
Import existing Stalwart domains and manage local records.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | List domains | domains:read |
POST/ | Import an existing domain | domains:write |
GET/ | Get a domain | domains:read |
DELETE/ | Remove the local domain record | domains:write |
GET/ | Get domain connection and DNS status | domains:read |
PUT/ | Enable or disable a domain | domains:write |
GET/ | Get a domain's catch-all | domains:read |
PUT/ | Set or clear a domain's catch-all | domains:write |
GET/ | List a domain's alias domains | domains:read |
POST/ | Request an alias domain | domains:write |
GET/ | Get an alias domain and its DNS status | domains:read |
DELETE/ | Remove an alias domain | domains:write |
POST/ | Verify ownership and add the alias domain | domains:write |
POST/ | Verify and enable domain transport security | domains:write |
Mailboxes
Manage mailboxes; passwords and redemption-code links are never returned.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | List mailboxes | mailboxes:read |
POST/ | Create a mailbox | mailboxes:write |
GET/ | Get a mailbox | mailboxes:read |
DELETE/ | Delete a mailbox | mailboxes:write |
GET/ | List deleted mailboxes | mailboxes:read |
POST/ | Restore a deleted mailbox | mailboxes:write |
PUT/ | Change mailbox password | mailboxes:write |
PUT/ | Change mailbox status | mailboxes:write |
PUT/ | Change mailbox quota | mailboxes:write |
POST/ | Set daily sending limits for multiple mailboxes | mailboxes:write |
GET/ | Get a mailbox's daily sending limit | mailboxes:read |
PUT/ | Set a mailbox's daily sending limit | mailboxes:write |
PUT/ | Change mailbox note | mailboxes:write |
Forwarders
Forwarding rules between single regular email addresses.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | List forwarding rules | forwarders:read |
POST/ | Create a forwarding rule | forwarders:write |
DELETE/ | Delete a forwarding rule | forwarders:write |
Aliases
Regular aliases delivering to managed mailboxes on the same domain.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | List regular aliases | aliases:read |
POST/ | Create a regular alias | aliases:write |
GET/ | Get a regular alias | aliases:read |
PATCH/ | Update a regular alias | aliases:write |
DELETE/ | Delete a regular alias | aliases:write |
Lists
Distribution lists: an address of a domain that delivers to all of its members.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | List distribution lists | lists:read |
POST/ | Create a distribution list | lists:write |
GET/ | Get a distribution list | lists:read |
PATCH/ | Update a distribution list | lists:write |
DELETE/ | Delete a distribution list | lists:write |
Legal holds
Legal holds: a held mailbox cannot be deleted permanently or purged. A hold preserves the mailbox, not individual messages.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | List legal holds | holds:read |
POST/ | Place a legal hold | holds:write |
GET/ | Get a legal hold | holds:read |
POST/ | Release a legal hold | holds:write |
Reports
Usage reports: inactive mailboxes, storage per domain, top senders. Computed from this app's own records.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | Inactive mailboxes | mailboxes:read |
GET/ | Storage per domain | mailboxes:read |
GET/ | Top senders by daily sending-limit usage | mailboxes:read |
Batch
Manage domains, mailboxes and aliases in ordered batches with persisted idempotent results.
| 操作 | 说明 | 权限范围 |
|---|---|---|
POST/ | Batch-manage domains, mailboxes and aliases | 按每项操作mailboxes:write, domains:write, aliases:write |
GET/ | Get batch execution status | 仅限创建该批次的密钥 |
Agent tasks
An agent follows up email as a given mailbox: it starts from a prompt, sends fully autonomously by default, and continues when replies arrive.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | List follow-up tasks | agent_tasks:read |
POST/ | Create a follow-up task | agent_tasks:write |
POST/ | Create a follow-up task for each of several mailboxes | agent_tasks:write |
GET/ | Get a follow-up task | agent_tasks:read |
PATCH/ | Change a follow-up task's limits | agent_tasks:write |
GET/ | Fetch the task timeline | agent_tasks:read |
GET/ | List the conversations a task follows | agent_tasks:read |
POST/ | Hand a conversation to a task | agent_tasks:write |
POST/ | Message the agent | agent_tasks:write |
POST/ | Pause a follow-up task | agent_tasks:write |
POST/ | Resume a follow-up task | agent_tasks:write |
POST/ | Cancel a follow-up task | agent_tasks:write |
GET/ | List emails awaiting approval | agent_tasks:read |
POST/ | Approve or reject an email | agent_tasks:write |
Webhooks
Signed event webhooks: mailbox, domain, order, bulk job and security events POSTed to your https address, with retries and a delivery log.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | List webhooks | webhooks:read |
POST/ | Create a webhook | webhooks:write |
GET/ | Get a webhook | webhooks:read |
PATCH/ | Update a webhook | webhooks:write |
DELETE/ | Delete a webhook | webhooks:write |
GET/ | List a webhook's recent delivery attempts | webhooks:read |
POST/ | Send a delivery's event again | webhooks:write |
POST/ | Send a test event | webhooks:write |
Trace
Message trace: the delivery timeline of messages sent or received by the workspace's domains, read from the mail server's logs and queue. Every call is recorded in the activity log.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | Trace messages | trace:read |
Emails
Send mail from the workspace's domains through their own mail server, and follow each message's delivery. Off until a platform admin approves the workspace; test keys send to the sandbox.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | List sent messages | emails:read |
POST/ | Send a message | emails:write |
POST/ | Send several messages | emails:write |
GET/ | Get a message's delivery status | emails:read |
GET/ | Get the workspace's sending state | emails:read |
GET/ | Get a domain's sending state | emails:read |
PUT/ | Turn sending on for a domain | emails:write |
DELETE/ | Turn sending off for a domain | emails:write |
GET/ | List a domain's SMTP relay credentials | emails:read |
POST/ | Make an SMTP relay credential | emails:write |
DELETE/ | Revoke an SMTP relay credential | emails:write |
Inbound
Inbound routing: mail that chosen mailboxes receive is POSTed to webhooks as message.received, and can be read through the API.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | List inbound routes | inbound:read |
GET/ | Get a mailbox's inbound route | inbound:read |
PUT/ | Turn inbound routing on for a mailbox | inbound:write |
DELETE/ | Turn inbound routing off for a mailbox | inbound:write |
GET/ | Read a received message | inbound:read |
GET/ | Download an attachment of a received message | inbound:read |
Agent mailboxes
A mailbox an AI agent reads and sends from, made in one call, and a key restricted to just that mailbox for the agent itself.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | List agent mailboxes | agent_mailboxes:read |
POST/ | Create an agent mailbox | agent_mailboxes:write |
GET/ | Get an agent mailbox | agent_mailboxes:read |
DELETE/ | Delete an agent mailbox | agent_mailboxes:write |
GET/ | List an agent's inbox | agent_mailboxes:read |
GET/ | Read a message an agent received | agent_mailboxes:read |
PATCH/ | Mark a message an agent received read or flagged | agent_mailboxes:write |
DELETE/ | Delete a message an agent received | agent_mailboxes:write |
GET/ | Download an attachment of a received message | agent_mailboxes:read |
POST/ | Reply to a message as the agent | agent_mailboxes:write |
POST/ | Send a message as the agent | agent_mailboxes:write |
POST/ | Create a key for one agent mailbox | agent_mailboxes:write |
Mail apps
Third-party apps a workspace registers so its mailbox users can authorize them, with OAuth on WebMail, to read their mail or send as them through WebMail's Mail API.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | List mail apps | mail_apps:read |
POST/ | Register a mail app | mail_apps:write |
GET/ | Get a mail app | mail_apps:read |
DELETE/ | Revoke a mail app | mail_apps:write |
Suppressions
The workspace's suppression list: recipients that hard-bounced repeatedly or were added by hand. Advisory: nothing is refused at submission; check an address before sending to it.
| 操作 | 说明 | 权限范围 |
|---|---|---|
GET/ | List suppressed recipients | suppressions:read |
POST/ | Suppress a recipient | suppressions:write |
GET/ | Check whether a recipient is suppressed | suppressions:read |
DELETE/ | Remove a recipient from the suppression list | suppressions:write |