快速开始

面向 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/whoamiAPI 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 全文,中文可用)、directionfromsince/until(ISO 时间)、has_attachmentsattachment_type(MIME 前缀)、headerunread=true|false(仅未读/仅已读)、bounce=true(仅退信/投递失败报告)、folder=trash(垃圾箱)、limit/offset。每封带 read_atis_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 里列出。响应含 updatedunread_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。响应含 providerthread_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/webhooksWebhook 管理(见下)
GET/POST/DELETE /api/keysAPI Key 管理:列表(含明文,可随时查看)/ 新建 / 删除(至少保留一个);每邮箱上限 5 个
GET/POST/DELETE /api/drafts服务端草稿:列表(正文只回 200 字符预览,带 body_bytestruncated;全文用 ?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/wsWebSocket 实时推送新邮件。用 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/startCLI 账号登录设备流:返回 {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。