快速开始
面向 AI Agent 的自托管邮件基础设施:认领邮箱 → 拿到 API key → 收发、搜索、验证码、附件、线程、Webhook 全部走 API。 邮箱归属你的账号(认领时登录,换设备可找回);日常调用只用 API key —— 拿着它在任何环境(本地 / 云端 / CI)都能独立使用。
01认领邮箱
浏览器:mails.offerdao.ai/claim,或用 API 设备流:
POST /api/claim/start {"address": "myagent"}
→ {claim_id, confirm_url, expires_at} # 打开 confirm_url,登录账号并完成人机验证
GET /api/claim/poll?id=<claim_id> # 轮询,approved 时返回 API key(字段名 token,之后可在「我的信箱」查看)
确认页需要登录账号(Google 或邮箱密码,与 offerdao.ai 通用,可现场注册)。邮箱归属该账号:换设备时在认领页「登录账号」一键找回全部邮箱;早期未绑定的邮箱可在「我的信箱 → 设置」补绑定。
02CLI
npm install -g @offerdao/mails mails claim myagent # 浏览器确认,API key 与配置自动写入 mails login # 换设备:用账号找回名下全部邮箱 mails accounts / mails use <邮箱> # 列出 / 切换当前邮箱 # (或手动配置已有 key:worker_url / worker_token / mailbox / default_from) mails send --to user@example.com --subject "Hello" --body "World" mails send --to a@b.com,c@d.com --cc boss@x.com --subject "Report" --attach report.pdf --body "见附件" mails send --to a@b.com --subject "长正文" --body-file ./report.md # 正文来自文件('-' 读 stdin) mails send --to a@b.com --subject "S" --body "x" --idempotency-key job-42 # 重试不会重复发送 mails send --to a@b.com --subject "S" --body "x" --at 2026-08-01T09:00:00Z # 定时发送(服务端) mails send --to a@b.com --subject "S" --body "x" --dry-run # 只打印信封,不发送 mails reply <id> --body "收到" # 自动填收件人/主题/引用/线程头 mails forward <id> --to peer@x.com --body "转给你" # 附件一起带走 mails draft new --to a@b.com --subject "草稿" --body "wip" # 先写好放草稿箱 mails draft / mails draft send <id> # 列草稿 / 发送并删除草稿(一次调用) mails inbox # 列表(● 未读,⚠ 退信) mails inbox --unread / --bounces # 只看未读 / 只看退信 mails inbox <id> --save ./dir # 详情 + 下载附件 mails inbox --query "验证码" # 全文搜索(FTS,中文可用) mails inbox --limit 50 --offset 50 # 翻页 mails watch # 实时流式接收新邮件(WebSocket,轮询兜底) mails code --to myagent@mails.offerdao.ai # 等验证码(长轮询) mails inbox --from cloudflare --since 2026-07-01T00:00:00Z --has-attachments mails threads / mails thread <id> # 线程 mails profile signature "Agent Bot" # 发件签名(--no-signature 单次跳过) mails quota # 本月发件额度(按收件人数计) mails webhooks add https://example.com/hook # HMAC 签名推送 mails export --out ./backup # 批量导出 .eml(--format mbox 导出单文件) mails delete <id> / mails mailbox destroy # 删除 / 注销 mails sync # 同步到本地 SQLite
所有命令都支持
--json(结构化输出,失败也是 JSON),Agent 请用它而不是解析文本表格;mails watch --json 每行一个 JSON。一台机器跑多个 agent?为每个进程设置 MAILS_CONFIG_DIR,各自持有独立的邮箱配置。Agent 接入说明可直接取 /skill.md。
03REST API
鉴权:Authorization: Bearer <api_key>;除 claim / health 外所有接口都需要。列表接口需 ?to=<你的邮箱>。
| 接口 | 说明 |
|---|---|
GET /api/whoami | API key → 邮箱地址 |
GET/PATCH /api/profile | 读取或设置发件人显示名称与签名;PATCH body:{"display_name":"求职 Agent"} 和/或 {"signature":"Agent Bot\nmails.offerdao.ai"}(只更新传入的字段,留空字符串即清除)。签名会追加到发件正文(text 用 -- 分隔行,html 追加一段),单次发送可用 no_signature 跳过 |
GET /api/inbox | 列表/搜索。参数:query(FTS 全文,中文可用)、direction、from、since/until(ISO 时间)、has_attachments、attachment_type(MIME 前缀)、header、unread=true|false(仅未读/仅已读)、bounce=true(仅退信/投递失败报告)、folder=trash(垃圾箱)、limit/offset。每封带 read_at 与 is_bounce |
GET /api/email?id= | 详情(含附件元数据),支持 id 前缀 |
DELETE /api/email?id= | 移入垃圾箱(30 天后自动清除);&hard=1 彻底删除(连带 R2 附件与原始 EML) |
PATCH /api/email?id= | 从垃圾箱恢复(同样支持 id 前缀) |
POST /api/read | 标记已读。body:{"ids":["…"]} 或 {"all":true}(整个收件箱);加 {"unread":true} 反标为未读。四个文件夹通用——ids 可以是收件、已发送、垃圾箱的邮件 id,也可以是草稿 id(草稿在自己的表里,同样有 read_at)。ids 接受 id 前缀(列表里的 8 位短 id 可直接用);匹配不到或有歧义的会在响应 unresolved 里列出。响应含 updated 与 unread_count(后者只统计收到的邮件) |
GET /api/unread | 收件箱未读数:{"unread_count":n}。只数收到的邮件——把已发送邮件或草稿标为未读(当作待跟进)不会让这个数字变化 |
GET /api/quota | 本月发件额度:{"used":n,"limit":100,"remaining":n,"period":"YYYY-MM","scope":"account|mailbox"}——绑定账号的邮箱按账号合计(scope=account),未绑定按单邮箱计(scope=mailbox) |
GET /api/code?to=&timeout= | 等待验证码,长轮询 ≤55s,支持 since |
POST /api/send | 发件。body:from,to[],subject,text/html,cc,bcc,reply_to,attachments[{filename,content(base64),content_type}],reply_to_email_id(回复自动带线程头)、idempotency_key(幂等键:同键重试直接重放首次结果并带 Idempotent-Replay: true,仍在处理中返回 409,发送失败则释放该键可重试)、no_signature。响应含 provider 与 thread_id |
GET /api/threads?to= | 线程列表(最新在前,含 message_count) |
GET /api/thread?id= | 线程内全部邮件(也接受邮件 id);&full=1 附带每封的正文与附件元数据 |
DELETE /api/thread?id= | 删除整个线程——硬删除(附件与原始 EML 一并清除),不进垃圾箱、不可恢复 |
GET /api/attachment?id= | 下载附件二进制(Content-Disposition 带文件名) |
GET /api/raw?id= | 原始 EML(message/rfc822,保留 90 天) |
GET /api/stats/senders?to= | 发件人统计 |
GET /api/sync?to=&since= | 增量同步(全字段) |
GET/POST/DELETE /api/webhooks | Webhook 管理(见下) |
GET/POST/DELETE /api/keys | API Key 管理:列表(含明文,可随时查看)/ 新建 / 删除(至少保留一个);每邮箱上限 5 个 |
GET/POST/DELETE /api/drafts | 服务端草稿:列表(正文只回 200 字符预览,带 body_bytes 与 truncated;全文用 ?id= 取单条)/ upsert(body:id?,to_address,cc,bcc,subject,body_text,is_html,reply_to_email_id,省略 id 则新建;attachments[{filename,content(base64),content_type}] —— 省略该字段=保留原附件,传 [] =清空,≤10 个 / 合计 ≤10MB;scheduled_at ISO 时间=排定发送,null =取消,最远 60 天)/ 按 ?id= 删除(附件一并清除);每邮箱上限 100 个 |
POST /api/drafts/send | 发送草稿并删除它(一次调用,body:{"id":"…","no_signature":false})。发送失败则草稿原样保留;缺收件人/主题返回 400,附件内容丢失返回 502 |
GET /api/draft-attachment?id=&index= | 下载草稿附件二进制 |
GET /api/ws | WebSocket 实时推送新邮件。用 Sec-WebSocket-Protocol: bearer, <token> 握手鉴权;保活由客户端定期发文本帧 ping,服务端自动回 pong |
DELETE /api/mailboxes | 注销自己的邮箱(级联删除 + 30 天冷却) |
GET /api/my/mailboxes | 账号名下的邮箱列表。此组端点鉴权用 Supabase JWT(Authorization: Bearer),不是邮箱 key |
POST /api/my/mailbox-tokens | 取回名下每个邮箱的可用 API key(控制台「登录账号」内部使用) |
POST /api/my/bind | 把已有邮箱绑定到当前账号(body:{"token": "<邮箱 API key>"};已绑他人则 409) |
POST /api/login/start | CLI 账号登录设备流:返回 {login_id, code, confirm_url, expires_at}(10 分钟有效)。mails login 用它;无需鉴权 |
GET /api/login/info?id= | 确认页读取待核对的 code 与状态 |
POST /api/login/confirm | 浏览器确认(需 Supabase JWT):把名下邮箱的 key 交给等待中的 CLI |
GET /api/login/poll?id= | CLI 轮询(限流 600 次/IP/小时);approved 时一次性返回 accounts[{mailbox,token,display_name}],读取后即失效 |
POST /api/login/deny | 确认页点取消时调用,把请求置为 denied,等待中的 CLI 立即得到结果而不必干等过期 |
04Webhook
注册后,新邮件到达时 POST message.received 事件到你的 URL(https),带 HMAC-SHA256 签名:
POST /api/webhooks {"url": "https://example.com/hook"}
→ {id, secret} # secret 仅返回一次
# 事件请求头
X-Mails-Event: message.received
X-Mails-Delivery: <uuid>
X-Mails-Signature: sha256=<hex>
# 验签(Node)
const ok = signature === 'sha256=' + crypto
.createHmac('sha256', secret).update(rawBody).digest('hex')
投递失败自动重试 2 次;事件 payload 含 id/from/subject/code 摘要,全文再调 /api/email 取。
05限制与配额
| 项 | 值 |
|---|---|
| 发件 | 默认每月 100 收件人/账号(名下所有邮箱合计;未绑定账号的邮箱按每邮箱 100),按 to/cc/bcc 合计(429 超限);用量见 /api/quota |
| 单封收件人 | 每封最多 25 个(to/cc/bcc 合计),超出返回 400 |
| claim | 需登录账号;每账号最多 5 个邮箱;10 次/IP/小时;保留字地址不可认领 |
| 正文存储 | text 50KB / html 100KB 截断 |
| 草稿 | ≤100 个/邮箱;每草稿 ≤10 个附件、合计 ≤10MB;定时最远 60 天,到点后由 cron 在 5 分钟内发出 |
| 幂等键 | ≤128 字符,凭据保留 7 天(之后同键会被视为新请求) |
| 签名 | ≤2000 字符 |
| 原始 EML | 保留 90 天后自动清理 |
| 注销冷却 | 地址注销后 30 天内不可重新认领 |
| webhook | ≤5 个/邮箱,仅 https |
开源基座:chekusu/mails(MIT)· 部署于 Cloudflare Workers + D1 + R2 + Email Routing。