- 单一 Key:API 与 MCP 共用一个 wmh_ Key,凭据迁移至 settings 表(0004 删除 api_credentials) - Markdown:入库时 HTML→Markdown(mailhook 同思路),webhook payload 新增 markdown 字段 - /emails 路由改为 /inboxs 并支持域名筛选;设置按钮移入头像下拉 - 主题切换按钮(头像左侧,亮/暗持久化);设置模态仅 全局 Webhook/账号密码 两 tab 且高度限屏 - 列表操作列按钮加 SVG 图标;mailboxes 去 Webhook 列;最近邮件加收件人列
WmailHub
基于 Cloudflare Email Workers 的轻量邮件接收与转发平台。
Internet → Cloudflare Email Routing → Email Worker → Console → D1/R2
(catch-all) (authorize/ingest) ├→ Webhook
├→ API v1
└→ MCP
- Console(唯一业务控制中心)= 前端(Vite + React + antd v6)+ 管理 API + 内部邮件 API(
/internal/email/authorize//internal/email/ingest),部署在 Cloudflare Workers,负责 Cloudflare OAuth、账号/域名/邮箱/邮件管理、数据存储与 TTL 清理。 - Email Worker = 邮件入口 + SMTP reject 执行者:先向 Console authorize 询问是否接收,明确拒绝时
message.setReject(),允许时把 raw MIME 提交 ingest;超时/5xx 一律按临时错误,不伪装成邮箱不存在。
v2.0.0 起 Node 已从邮件架构中移除(旧
Email Worker → Node → Console链路废弃,docs/11)。 Email Worker 直连 Console;「账号」= Connect Cloudflare Account(OAuth 自动接入, 自动部署账号级 Email Worker 并签发 Worker 凭据)。
本地开发(无需 Cloudflare 账号)
bun install
bun run dev # 一键启动:console + email worker + 自动 OAuth(sim)接入
bun run dev -- --reset # 清空本地数据重新接入
bun test # 测试套件(协议安全/authorize 决策/管线/TTL/Webhook/MCP/OAuth)
bun tests/ui-smoke.ts # UI 冒烟(需本机 Playwright chromium)
启动后:
| 服务 | 地址 |
|---|---|
| Console(前端 + API) | http://127.0.0.1:18787 |
| Email Worker(本地联调) | http://127.0.0.1:18789/__health |
登录:默认 admin / admin。
发测试邮件(走完整两阶段链路 Worker→authorize→ingest):
COOKIE=$(curl -si -X POST http://127.0.0.1:18787/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"admin"}' \
| grep -i '^set-cookie' | sed 's/^.*wmh_session=\([^;]*\).*/wmh_session=\1/')
ACC=$(curl -s http://127.0.0.1:18787/api/accounts -H "Cookie: $COOKIE" \
| python3 -c "import sys,json;print(json.load(sys.stdin)['items'][0]['id'])")
SECRET=$(curl -s http://127.0.0.1:18787/api/accounts/$ACC/worker-secret \
-H "Cookie: $COOKIE" | python3 -c "import sys,json;print(json.load(sys.stdin)['secret'])")
curl -X POST http://127.0.0.1:18789/__email_test \
-H "X-WmailHub-Test-Secret: $SECRET" \
-H "X-WmailHub-Test-From: alice@example.org" \
-H "X-WmailHub-Test-To: hello@example.test" \
--data-binary @tests/fixtures/test-mail.eml
# {"action":"accepted","email_id":"msg_xxx","mailbox_id":"mbx_xxx"}
# 不存在的邮箱:{"action":"rejected","reason":"Mailbox does not exist","code":"MAILBOX_NOT_FOUND"}
bun run dev 首次运行会自动:Connect Cloudflare Account(sim 模式 OAuth)→ 同步账号下全部域名 + catch-all 邮箱 → 逐域名配置 Email Workers(部署账号级 Worker + Email Routing + catch-all 规则)→ 签发 WorkerCredential → 启动本地 Email Worker 直连 Console。全程无需复制任何安装命令。
功能
- 用户登录:用户名密码(PBKDF2),默认 admin/admin,HMAC 签名会话 cookie;密码在「设置」中修改。
- 账号管理:「添加账号」直达 Cloudflare OAuth → 自动创建账号并同步该账号下全部域名、部署 Email Worker、签发 Worker 凭据;支持凭据轮换(轮换后已配置域名自动重新部署)。
- 域名:由 OAuth 自动同步(无手工添加);点击域名「设置 Email Workers」= 确保账号级 Worker 部署 + 开启 Email Routing + catch-all 规则;支持按账号筛选。
- 两阶段接收:authorize(SMTP 决策:accept/reject/temporary_error,拒绝码 DOMAIN_NOT_FOUND / DOMAIN_DISABLED / MAILBOX_NOT_FOUND / MAILBOX_DISABLED / RECEIVING_DISABLED)+ ingest(raw MIME 入库);动态邮箱创建/删除/禁用下一封邮件立即生效。
- 邮件管线:MIME 解析(postal-mime)、附件、catch-all/精确匹配、大小限额、malformed 降级入库。
- TTL:邮件默认保留 1 天(
expires_at),Cron 定期清理 R2 + DB + orphan,幂等。 - Webhook(三级):配置落在 邮箱 → 域名 → 全局(设置)三级,收信时取第一个命中者转发
email.received事件;支持自定义请求头与 key(key 默认以Authorization: Bearer <key>携带,并用于X-WmailHub-Signature签名);指数退避重试,SSRF 防护。 - API v1:
Authorization: Bearer wmh_...访问/api/v1/*(/accounts /domains /mailboxes /emails)。 - MCP:
/api/v1/mcp(JSON-RPC over HTTP),工具:list_mailboxes/list_emails/get_email/create_mailbox/delete_mailbox。 - Key 管理:API Key 与 MCP Key 在「设置」模态窗口内生成/撤销/删除,
wmh_前缀 + 随机串,总长 32 位;库中只存 SHA-256,明文仅创建时展示一次。
Worker <-> Console 协议(docs/04)
body_hash = SHA256(raw_body)
canonical = METHOD \n PATH \n TIMESTAMP \n NONCE \n body_hash
signature = HMAC-SHA256(worker_credential_secret, canonical)
请求头:Authorization: WmailHub-Worker <worker_id>、X-WmailHub-Timestamp(±5 分钟)、X-WmailHub-Nonce(防重放)、X-WmailHub-Body-SHA256、X-WmailHub-Signature、X-WmailHub-Protocol: 1。
内部 API:
POST /internal/email/authorize { "recipient": "...", "sender": "..." } → accept | reject | temporary_error
POST /internal/email/ingest raw MIME(message/rfc822)+ X-WmailHub-Recipient / X-WmailHub-Sender
Console 校验:Worker ID、timestamp 窗口、nonce 防重放、body hash、HMAC、credential 状态(撤销立即生效)、Worker 所属账号与收件域名一致。
目录结构
WmailHub/
console/ Console(Workers 入口 src/worker.ts + 本地运行时 src/local/main.ts + 前端 frontend/)
worker/ Email Worker(Cloudflare 部署模板 shared/src/email-worker-template.ts + 本地联调 local.ts)
shared/ 共享协议:HMAC 签名 / Key 生成 / 加密 / SSRF 防护 / Email Worker 脚本模板
migrations/ D1 schema(0001 初始 + 0002 三级 Webhook + 0003 邮件链路 v2)
tests/ bun test 测试 + UI 冒烟 + fixtures
scripts/ dev 启动(前端构建走 vite)
docs/ 设计文档(00~11;11 为邮件架构改造方案)
生产部署(Cloudflare Workers)
bunx wrangler d1 create wmailhub→ 将database_id填入console/wrangler.jsoncbunx wrangler r2 bucket create wmailhub- 依次执行
bunx wrangler d1 execute wmailhub --remote --file=./migrations/0001_init.sql、0002_accounts_webhooks.sql、0003_mail_link_v2.sql cd console && bunx wrangler deploy(前端先bun run build);PUBLIC_BASE_URL即 Email Worker 注入的 CONSOLE_URLbunx wrangler secret put SESSION_SECRET --config console/wrangler.jsonc(≥32 位随机串)- Cloudflare Dashboard 创建 OAuth App(client id/secret),配置
CF_OAUTH_CLIENT_ID/SECRETvars 与回调<PUBLIC_BASE_URL>/api/oauth/cloudflare/callback;未配置时自动进入 sim 模式(仅供本地)
Cron(*/1 * * * *)驱动 TTL 清理与 Webhook 重试;Email Worker 由 Console provisioning 自动上传(无需手工部署),catch-all 规则在「域名 → 设置 Email Workers」时自动配置。
安全设计(docs/08)
- OAuth token、Worker credential、Webhook key 全部 AES-256-GCM 加密存储;凭据撤销/轮换即时生效。
- Worker→Console 强制 HTTPS + HMAC 签名 + timestamp 窗口 + nonce 防重放 + body hash 防篡改 + 账号归属校验。
- Worker 只持有自身凭据,不持有 Cloudflare OAuth 凭据;Console 拒绝文案不泄露内部信息。
- Webhook endpoint 防 SSRF(私网/loopback/metadata/非法 scheme;本地联调需显式
WMAILHUB_ALLOW_PRIVATE_WEBHOOKS=1)。 - 会话 cookie
SameSite=Strict+ 跨域 Origin 校验;日志不落 secret 与邮件正文。