feat: v2.0.0 邮件接收架构改造 —— 移除 Node,Email Worker 直连 Console(docs/11)
- 两阶段接收:POST /internal/email/authorize(accept/reject/temporary_error)+ POST /internal/email/ingest(raw MIME 直传) - Worker-Console 协议:Authorization: WmailHub-Worker + timestamp/nonce/body-hash/HMAC,nonce 防重放,凭据撤销即时生效 - 数据模型:nodes/node_credentials -> cloudflare_accounts/worker_credentials(迁移 0003),emails/domains 归属 account_id - 接入流程 Connect Cloudflare Account:OAuth 自动同步域名 + 部署账号级 Email Worker + 签发 Worker 凭据;rotate 自动重部署 - 动态邮箱:创建/删除/禁用下一封邮件立即生效;拒绝码 DOMAIN_*/MAILBOX_*;超时/5xx 不伪装成邮箱不存在 - 删除 node/ 目录、旧 /api/internal/nodes 协议、ingress/enrollment 概念;dev.ts 简化为 console + email worker 双进程 - 前端:账号页 Email Worker/凭据管理,设置窗口移除默认 Ingress,仪表盘/文案同步;版本 2.0.0 - 测试 62 项全过(新增 authorize 决策矩阵 + Worker 两阶段临时错误语义);本机虚拟 E2E 12 场景全通 - docs 00-10 同步改写;README 更新
This commit is contained in:
+35
-40
@@ -1,18 +1,19 @@
|
||||
# WmailHub 开发设计总览
|
||||
# WmailHub 开发设计总览(v2,docs/11)
|
||||
|
||||
## 1. 项目定位
|
||||
|
||||
WmailHub 是一个基于 Cloudflare Email Workers 的轻量邮件接收与转发平台。
|
||||
|
||||
核心原则:
|
||||
核心原则:
|
||||
|
||||
- Cloudflare 负责邮件基础设施。
|
||||
- Console 是控制平面,包含前端与管理 Node。
|
||||
- Node 是邮件数据转发平面,无前端、无业务判断。
|
||||
- Console 通过 Cloudflare OAuth 自动完成 Node、Domain、Mailbox、Email Worker 的配置。
|
||||
- Node 只能处理被授权 Cloudflare Account 下的域名邮箱。
|
||||
- Node 与 Console 的通信必须使用 HTTPS + 应用层签名。
|
||||
- 邮件默认短期保存,例如 1 天,过期自动删除。
|
||||
- 不自建 SMTP、IMAP、POP3。
|
||||
- 不部署邮件转发 Node(v1 的 Node 已从邮件链路移除)。
|
||||
- Email Worker 是邮件入口 + SMTP reject 执行者。
|
||||
- Console 是唯一业务控制中心,也是"是否允许接收邮件"的唯一事实来源。
|
||||
- Worker 不保存邮箱业务数据;邮箱、域名、启用状态全部由 Console 管理。
|
||||
- Console 明确允许时,Worker 接收邮件;明确拒绝时执行 `message.setReject()`;
|
||||
Console 超时或 5xx 不能被解释成邮箱不存在。
|
||||
- 邮件默认短期保存,例如 1 天,过期自动删除。
|
||||
- 邮件进入 Console 后才能进入 Webhook、API、MCP 等业务能力。
|
||||
|
||||
## 2. 总体架构
|
||||
@@ -25,14 +26,12 @@ Cloudflare Email Routing
|
||||
|
|
||||
v
|
||||
Cloudflare Email Worker
|
||||
|
|
||||
| HTTPS + HMAC(authorize / ingest)
|
||||
v
|
||||
Node
|
||||
| HTTPS + Request Signature
|
||||
v
|
||||
Console Management Node
|
||||
Console
|
||||
|
|
||||
+--> Database
|
||||
+--> D1
|
||||
+--> R2
|
||||
+--> Webhook
|
||||
+--> API
|
||||
+--> MCP
|
||||
@@ -41,60 +40,56 @@ Console Management Node
|
||||
|
||||
## 3. Console
|
||||
|
||||
Console = Frontend + Management Node。
|
||||
Console = Frontend + Management API。
|
||||
|
||||
负责:Cloudflare OAuth、Cloudflare Account、Node、Domain、Mailbox、Email、Webhook、API、MCP、Node 安装与配置、数据存储、TTL 清理调度。
|
||||
负责:Cloudflare OAuth、Cloudflare Account、Domain、Mailbox、邮箱启用/禁用、Email Storage、
|
||||
Webhook、REST API、MCP、TTL Cleanup、Cloudflare Email Routing 配置、Email Worker 部署、
|
||||
Worker Credential。
|
||||
|
||||
用户不执行 Node 安装命令。点击 Add Node 后进入 Cloudflare OAuth,授权后由 Cloudflare API 自动完成安装和配置。
|
||||
用户不执行安装命令。点击「添加账号」进入 Cloudflare OAuth(Connect Cloudflare Account),
|
||||
授权后由 Cloudflare API 自动完成域名同步、Worker 部署与凭据配置。
|
||||
|
||||
## 4. Node
|
||||
## 4. Email Worker
|
||||
|
||||
Node 是纯后端组件,只做:接收 Cloudflare Email Worker 的邮件、将邮件安全转发到 Console。
|
||||
Worker 只做:接收 Cloudflare Email Routing 邮件 -> authorize(询问 Console)->
|
||||
reject 时 `message.setReject()` -> accept 时把 raw MIME 提交 ingest。
|
||||
|
||||
Node 不做 Domain 判断、Mailbox 判断、邮件业务路由、Webhook、MCP、API、用户权限、邮件长期存储。
|
||||
|
||||
Node 收到什么就转发什么。
|
||||
不做邮箱/域名管理、用户权限、Webhook、API、MCP、D1/R2 业务查询、TTL、邮箱业务规则。
|
||||
|
||||
## 5. 权限边界
|
||||
|
||||
一个 Node 与一个 Cloudflare Account 建立绑定。Node A 只能处理 Account A 下配置的 Email Worker,不允许处理其他 Account。
|
||||
一个账号绑定一个 Cloudflare Account 与一个 Email Worker;Worker credential 只能处理
|
||||
所属账号下的域名邮箱(docs/04 归属校验)。
|
||||
|
||||
Domain 与 Mailbox 的具体业务信息由 Console 管理,Node 不需要理解这些业务字段。
|
||||
|
||||
## 6. 核心邮件流程
|
||||
## 6. 核心邮件流程(两阶段)
|
||||
|
||||
```text
|
||||
Email
|
||||
|
|
||||
v
|
||||
Cloudflare Email Routing
|
||||
Cloudflare Email Routing(catch-all)
|
||||
|
|
||||
v
|
||||
Email Worker
|
||||
|
|
||||
v
|
||||
Node
|
||||
|
|
||||
v
|
||||
Console
|
||||
|
|
||||
+--> Store
|
||||
+--> Webhook
|
||||
+--> API
|
||||
+--> MCP
|
||||
Console /internal/email/authorize
|
||||
+-- accept -> /internal/email/ingest -> D1 + R2 + Webhook
|
||||
+-- reject -> message.setReject()
|
||||
+-- temporary_error -> 交由 Cloudflare 邮件机制重试
|
||||
```
|
||||
|
||||
## 7. 生命周期
|
||||
|
||||
默认邮件 TTL 为 1 天。数据库记录 expires_at,Cron 定期删除过期记录及对应 R2 对象。
|
||||
默认邮件 TTL 为 1 天。数据库记录 expires_at,Cron 定期删除过期记录及对应 R2 对象。
|
||||
|
||||
## 8. 第一版明确不做
|
||||
|
||||
- 自建 SMTP Server
|
||||
- IMAP / POP3
|
||||
- 发件服务器
|
||||
- 发件服务器 / NDR(反站)
|
||||
- 完整邮箱客户端
|
||||
- 复杂邮件规则引擎
|
||||
- AI 邮件分析
|
||||
- 多租户复杂组织系统
|
||||
- Node 端业务路由
|
||||
- KV/DO 邮箱缓存与同步(性能需要时再评估,须接受传播延迟)
|
||||
|
||||
+41
-21
@@ -1,31 +1,57 @@
|
||||
# WmailHub 系统架构
|
||||
# WmailHub 系统架构(v2,docs/11)
|
||||
|
||||
最终架构是两层邮件接收:
|
||||
|
||||
```text
|
||||
Internet
|
||||
|
|
||||
v
|
||||
Cloudflare Email Routing
|
||||
|
|
||||
v
|
||||
Email Worker
|
||||
|
|
||||
| HTTPS + HMAC(authorize / ingest)
|
||||
v
|
||||
WmailHub Console
|
||||
|
|
||||
+-- D1
|
||||
+-- R2
|
||||
+-- Webhook
|
||||
+-- API
|
||||
+-- MCP
|
||||
```
|
||||
|
||||
Node 已从邮件架构中移除(v1 的 `Email Worker -> Node -> Console` 链路废弃)。
|
||||
|
||||
## Console
|
||||
|
||||
Console = Frontend + Management Node,是系统控制中心。
|
||||
Console = Frontend + Management API,是系统唯一业务控制中心。
|
||||
|
||||
负责 Cloudflare OAuth、Node、Domain、Mailbox、Email、Webhook、API、MCP 以及数据存储。
|
||||
负责 Cloudflare OAuth、Cloudflare Account、Domain、Mailbox、邮箱启用/禁用、Email Storage(D1 + R2)、Webhook、REST API、MCP、TTL Cleanup、Email Routing 配置、Email Worker 部署与 Worker Credential。
|
||||
|
||||
## Node
|
||||
Console 是"是否允许接收邮件"的唯一事实来源。
|
||||
|
||||
Node 是纯后端、无前端的邮件转发节点。
|
||||
## Email Worker
|
||||
|
||||
唯一核心流程:Cloudflare Email Worker -> Node -> Console。
|
||||
Email Worker 是邮件入口 + SMTP reject 执行者,只负责:
|
||||
|
||||
Node 不负责 Domain、Mailbox、Webhook、MCP、API、用户权限或业务路由。
|
||||
1. 接收 Cloudflare Email Routing 邮件,读取 `message.from` / `message.to`;
|
||||
2. 调用 Console `POST /internal/email/authorize` 询问是否接收;
|
||||
3. 明确拒绝时执行 `message.setReject()`;
|
||||
4. 明确允许时把 raw MIME 提交到 Console `POST /internal/email/ingest`;
|
||||
5. 处理内部异常(超时/5xx 一律按临时错误,不伪装成邮箱不存在)。
|
||||
|
||||
## Cloudflare Worker
|
||||
|
||||
Email Worker 是邮件入口,只负责接收 Cloudflare Email Event 并安全转发给 Node。
|
||||
Worker 不创建邮箱、不做域名管理、不存业务数据、不实现 Webhook/API/MCP。
|
||||
|
||||
## 控制平面
|
||||
|
||||
```text
|
||||
Cloudflare Account
|
||||
+-- Node
|
||||
+-- Domain
|
||||
+-- Mailbox
|
||||
+-- Email Worker
|
||||
+-- Email Worker(账号级一个)
|
||||
+-- WorkerCredential
|
||||
```
|
||||
|
||||
这些资源关系由 Console 管理。
|
||||
@@ -33,15 +59,9 @@ Cloudflare Account
|
||||
## 数据平面
|
||||
|
||||
```text
|
||||
Email Worker -> Node -> Console
|
||||
Email Worker -> Console(authorize + ingest)-> D1 / R2 / Webhook
|
||||
```
|
||||
|
||||
Node 不参与业务决策。
|
||||
## 归属范围
|
||||
|
||||
## Node Scope
|
||||
|
||||
第一版一个 Node 绑定一个 Cloudflare Account。Node A 不允许处理 Account B 的资源。
|
||||
|
||||
## 扩展
|
||||
|
||||
未来可以有多个 Node,例如 Node JP、Node US、Node EU。每个 Node 默认仍绑定一个 Cloudflare Account。
|
||||
一个账号绑定一个 Cloudflare Account 与一个 Email Worker;Worker credential 不允许处理其它账号的域名。
|
||||
|
||||
@@ -1,11 +1,34 @@
|
||||
# Cloudflare OAuth 与自动安装
|
||||
# Cloudflare OAuth 与自动接入(docs/11 §14)
|
||||
|
||||
Console 的 Add Node 入口必须打开 Cloudflare OAuth 授权页面。用户授权后,Console 通过 Cloudflare API 自动完成 Node、Email Worker、Domain、Mailbox 的配置。
|
||||
旧「Add Node」概念已删除,改为 **Connect Cloudflare Account**。
|
||||
|
||||
用户不执行 shell 安装命令,也不手工创建 API Token。
|
||||
## 流程
|
||||
|
||||
流程:Console -> Add Node -> Cloudflare OAuth -> 授权回调 -> 获取 Account/Zone -> 创建 Node Identity -> 创建 Node Credential -> 创建或更新 Email Worker -> 配置 Email Routing -> 创建 Domain/Mailbox -> Node Ready。
|
||||
```text
|
||||
Console
|
||||
|
|
||||
v
|
||||
Cloudflare OAuth
|
||||
|
|
||||
v
|
||||
用户授权 Account
|
||||
|
|
||||
v
|
||||
Console
|
||||
+-- 保存 Account
|
||||
+-- Account / Zone 发现
|
||||
+-- 配置 Domain(同步账号下全部域名,pending)
|
||||
+-- 创建 catch-all Mailbox
|
||||
+-- 部署账号级 Email Worker
|
||||
+-- 签发 WorkerCredential(secret 注入 Worker 脚本)
|
||||
```
|
||||
|
||||
Cloudflare credential 与 Node credential 必须分离。Cloudflare credential 加密存储,不进入前端和日志。
|
||||
用户不需要执行安装命令,也不手工创建 API Token。
|
||||
|
||||
安装过程必须幂等,重复执行不能创建重复资源。OAuth state、授权回调和 Account ownership 必须验证。
|
||||
## 安全
|
||||
|
||||
- Cloudflare credential(OAuth access token)AES-GCM 加密存储,不进入前端和日志。
|
||||
- OAuth state 一次性(用后即删)、5 分钟过期、绑定账号(CSRF 防护)。
|
||||
- Account ownership 以 token 实际可访问的 accounts 为准。
|
||||
- 安装过程幂等,重复执行不产生重复资源。
|
||||
- Worker credential 与 Cloudflare credential 分离;轮换 credential 后已配置域名自动重新部署。
|
||||
|
||||
+19
-37
@@ -1,44 +1,26 @@
|
||||
# Node 设计
|
||||
# Node(已移除,docs/11 §4)
|
||||
|
||||
Node 是无前端、纯后端的邮件转发节点。
|
||||
v2 起 WmailHub 邮件架构中**不存在 Node**。旧方案 `Cloudflare -> Email Worker -> Node -> Console`
|
||||
废弃,新方案为 `Cloudflare -> Email Worker -> Console`。
|
||||
|
||||
核心流程:
|
||||
## 为什么删除 Node
|
||||
|
||||
```text
|
||||
Email Worker -> Node -> Console
|
||||
```
|
||||
Node 原来的职责只有「接收邮件 -> 转发邮件」,却额外引入:
|
||||
|
||||
## Node 职责
|
||||
- 一次网络跳转
|
||||
- 一套部署
|
||||
- 一套认证
|
||||
- 一个故障点
|
||||
- 一套重试
|
||||
- 一套日志
|
||||
- 一套版本管理
|
||||
|
||||
- 接收 Email Worker 请求
|
||||
- 验证 Worker 请求
|
||||
- 获取邮件
|
||||
- 安全转发到 Console
|
||||
- 有限重试临时网络错误
|
||||
- 心跳
|
||||
- 上报版本和状态
|
||||
Email Worker 已经能够接收邮件、读取 envelope sender/recipient、执行业务前置判断(authorize)、
|
||||
调用 HTTPS API(ingest)、执行 `message.setReject()` 并把原始邮件交给 Console,因此 Node 没有独立价值。
|
||||
|
||||
## Node 不负责
|
||||
## 现状
|
||||
|
||||
- Domain 路由
|
||||
- Mailbox 路由
|
||||
- Webhook
|
||||
- MCP
|
||||
- API
|
||||
- 用户权限
|
||||
- 邮件业务存储
|
||||
- 邮件规则
|
||||
|
||||
Node 收到什么就转发什么。
|
||||
|
||||
## Node Scope
|
||||
|
||||
Node 只能服务创建它的 Cloudflare Account。不得通过普通业务请求把 Node 任意绑定到其他 Account。
|
||||
|
||||
## 配置
|
||||
|
||||
Node 至少需要 node_id、cloudflare_account_id、console_endpoint、credential。
|
||||
|
||||
credential secret 必须安全保存。
|
||||
|
||||
Node 可以有限重试 Console 网络错误,但不承担长期邮件队列职责。
|
||||
- Console 的 `/api/internal/nodes/*` 内部协议与 `/internal/enroll` 端点已删除。
|
||||
- 数据模型不再有 nodes/node_credentials;账号实体为 cloudflare_accounts,
|
||||
Email Worker 与 Console 的认证凭据为 worker_credentials(docs/05)。
|
||||
- 账号概念保留:「账号」= 已连接的 Cloudflare Account(Connect Cloudflare Account 流程,docs/02)。
|
||||
|
||||
+25
-32
@@ -1,30 +1,12 @@
|
||||
# Node 与 Console 通信协议
|
||||
# Email Worker <-> Console 内部协议(docs/11 §8,协议版本 1)
|
||||
|
||||
Node -> Console 必须使用 HTTPS + HMAC 请求签名 + timestamp + nonce + body hash。
|
||||
`/internal/email/authorize` 与 `/internal/email/ingest` 都是内部 API,不能匿名访问。
|
||||
每个请求必须满足:HTTPS + Worker ID + timestamp + nonce + body SHA256 + HMAC-SHA256。
|
||||
|
||||
## Credential
|
||||
## 请求头
|
||||
|
||||
```text
|
||||
node_id
|
||||
credential_id
|
||||
credential_secret
|
||||
cloudflare_account_id
|
||||
```
|
||||
|
||||
Console 保存 secret 的安全摘要;Node 保存实际 secret。
|
||||
|
||||
## 签名
|
||||
|
||||
```text
|
||||
body_hash = SHA256(raw_body)
|
||||
canonical = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + body_hash
|
||||
signature = HMAC-SHA256(secret, canonical)
|
||||
```
|
||||
|
||||
请求头:
|
||||
|
||||
```text
|
||||
Authorization: WmailHub-Node <node_id>
|
||||
Authorization: WmailHub-Worker <worker_id>
|
||||
X-WmailHub-Timestamp: <unix_seconds>
|
||||
X-WmailHub-Nonce: <random>
|
||||
X-WmailHub-Body-SHA256: <hex>
|
||||
@@ -32,18 +14,29 @@ X-WmailHub-Signature: <hex>
|
||||
X-WmailHub-Protocol: 1
|
||||
```
|
||||
|
||||
Console 拒绝 timestamp 超窗、重复 nonce、错误 signature、错误 body hash、已撤销 credential。默认 timestamp window 建议 5 分钟。
|
||||
## 签名
|
||||
|
||||
```text
|
||||
body_hash = SHA256(raw_body)
|
||||
canonical = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + body_hash
|
||||
signature = HMAC-SHA256(worker_credential_secret, canonical)
|
||||
```
|
||||
|
||||
## Console 必须校验
|
||||
|
||||
1. Worker ID(存在且 credential active);
|
||||
2. timestamp(默认窗口 5 分钟);
|
||||
3. nonce 不重复(唯一索引入库,主键冲突即 replay);
|
||||
4. body hash;
|
||||
5. HMAC;
|
||||
6. credential 状态(撤销立即生效);
|
||||
7. Worker 所属 Cloudflare 账号与收件域名一致。
|
||||
|
||||
## API
|
||||
|
||||
```text
|
||||
POST /api/internal/nodes/{node_id}/emails
|
||||
POST /api/internal/nodes/{node_id}/heartbeat
|
||||
GET /api/internal/nodes/{node_id}/config
|
||||
POST /internal/email/authorize { "recipient": "...", "sender": "...", "message_id": "..." }
|
||||
POST /internal/email/ingest raw MIME(message/rfc822),收件人经 X-WmailHub-Recipient/Sender 头传递
|
||||
```
|
||||
|
||||
邮件请求推荐 `Content-Type: message/rfc822`,body 直接使用 raw MIME。
|
||||
|
||||
Console 可以立即撤销 credential。Node 收到 401/403 后不得无限重试。
|
||||
|
||||
协议必须版本化。
|
||||
Console 收到 401/403 的 Worker 应视为凭据失效,不得无限重试。协议必须版本化。
|
||||
|
||||
+40
-16
@@ -1,34 +1,44 @@
|
||||
# 数据模型
|
||||
# 数据模型(v2,docs/11 §13)
|
||||
|
||||
核心实体:CloudflareAccount、Node、Domain、Mailbox、Email、EmailAttachment、Webhook、WebhookDelivery、OAuthConnection。
|
||||
核心实体:CloudflareAccount、Domain、Mailbox、Email、EmailAttachment、Webhook(三级配置)、WebhookDelivery、OAuthConnection、WorkerCredential。
|
||||
|
||||
## nodes
|
||||
Node 作为邮件链路核心模型已删除(旧 nodes / node_credentials 表由迁移 0003 移除,
|
||||
账号实体更名为 cloudflare_accounts,凭据更换为 worker_credentials)。
|
||||
|
||||
## cloudflare_accounts
|
||||
|
||||
```text
|
||||
id, name, cloudflare_account_id, credential_id, status, version, last_seen_at, created_at, updated_at
|
||||
id, name, cloudflare_account_id, worker_id, status, provision_steps, created_at, updated_at
|
||||
```
|
||||
|
||||
status:pending_oauth | provisioning | active | error | revoked。
|
||||
worker_id 同时是 Email Worker 脚本名与 Worker -> Console 的身份 ID。
|
||||
|
||||
## domains
|
||||
|
||||
```text
|
||||
id, cloudflare_account_id, node_id, name, status, worker_id, created_at, updated_at
|
||||
id, cloudflare_account_id, account_id, name, status, worker_id, zone_id,
|
||||
webhook_url, webhook_headers, webhook_key_enc, setup_error, created_at, updated_at
|
||||
```
|
||||
|
||||
status:pending(未配置 Email Workers)| active | error。
|
||||
|
||||
## mailboxes
|
||||
|
||||
```text
|
||||
id, domain_id, address, status, retention_seconds, created_at, updated_at
|
||||
id, domain_id, address, status, retention_seconds, is_catch_all,
|
||||
webhook_url, webhook_headers, webhook_key_enc, created_at, updated_at
|
||||
```
|
||||
|
||||
## emails
|
||||
|
||||
```text
|
||||
id, node_id, mailbox_id nullable, message_id, from_address, to_addresses,
|
||||
id, account_id, mailbox_id nullable, message_id, from_address, to_addresses,
|
||||
cc_addresses, subject, text_body, html_body, raw_storage_key, size,
|
||||
received_at, expires_at, created_at
|
||||
```
|
||||
|
||||
Node 可以不知道 mailbox_id。Console 收到邮件后,根据邮件内容和自身配置关联。
|
||||
Console 收到邮件后根据收件人/内容与自身配置关联 mailbox。
|
||||
|
||||
## email_attachments
|
||||
|
||||
@@ -36,17 +46,17 @@ Node 可以不知道 mailbox_id。Console 收到邮件后,根据邮件内容
|
||||
id, email_id, filename, content_type, size, storage_key, content_id
|
||||
```
|
||||
|
||||
## webhooks
|
||||
## Webhook(三级:邮箱 -> 域名 -> 全局)
|
||||
|
||||
域名/邮箱行内配置(webhook_url / webhook_headers / webhook_key_enc),
|
||||
全局配置存 settings.global_webhook;独立 webhooks 实体已移除。
|
||||
|
||||
```text
|
||||
id, name, endpoint, secret, enabled, created_at, updated_at
|
||||
webhook_deliveries: id, email_id, target_type(mailbox|domain|global),
|
||||
url, headers, key_enc, status, attempt_count, next_retry_at, last_error, delivered_at, created_at
|
||||
```
|
||||
|
||||
## webhook_deliveries
|
||||
|
||||
```text
|
||||
id, webhook_id, email_id, status, attempt_count, next_retry_at, last_error, delivered_at, created_at
|
||||
```
|
||||
投递行在入队时冻结解析出的配置,重试不受后续配置变更影响。
|
||||
|
||||
## oauth_connections
|
||||
|
||||
@@ -54,4 +64,18 @@ id, webhook_id, email_id, status, attempt_count, next_retry_at, last_error, deli
|
||||
id, provider, account_id, encrypted_access_token, refresh_token, expires_at, created_at, updated_at
|
||||
```
|
||||
|
||||
OAuth token 必须加密存储。所有 Email 必须有 expires_at,默认 retention 为 86400 秒。
|
||||
OAuth token 必须加密存储。所有 Email 必须有 expires_at,默认 retention 为 86400 秒。
|
||||
|
||||
## worker_credentials
|
||||
|
||||
```text
|
||||
id, cloudflare_account_id, worker_id, secret_hash, secret_enc, secret_last4,
|
||||
status(active | revoked), created_at, revoked_at
|
||||
```
|
||||
|
||||
用于 Email Worker 与 Console 内部认证:secret_hash 为摘要,secret_enc 为 AES-GCM 密文
|
||||
(HMAC 验签需要原文,加密存储防拖库泄露);明文 secret 仅在签发/轮换时返回一次。
|
||||
|
||||
## 其它
|
||||
|
||||
users、settings、api_credentials(wmh_ Key)、nonces、oauth_states、cf_sim_resources(本地 sim)。
|
||||
|
||||
+29
-11
@@ -1,42 +1,60 @@
|
||||
# Email 生命周期与 TTL
|
||||
# Email 生命周期与 TTL(v2,docs/11 §3/§10)
|
||||
|
||||
## 接收
|
||||
## 接收(两阶段)
|
||||
|
||||
第一阶段只回答"这封邮件是否应该由 WmailHub 接收?":
|
||||
|
||||
```text
|
||||
Cloudflare -> Email Worker -> Node -> Console
|
||||
Cloudflare -> Email Worker
|
||||
-> POST /internal/email/authorize { recipient, sender }
|
||||
+-- accept 继续第二阶段
|
||||
+-- reject message.setReject()(SMTP 永久拒绝)
|
||||
+-- temporary_error 由 Cloudflare 邮件机制处理后续重试
|
||||
```
|
||||
|
||||
Console 验证 Node 身份和请求完整性,然后解析 MIME、提取 header、text/html 和附件,保存元数据,raw MIME/大附件写入 R2,计算 expires_at,并创建 Webhook Delivery。
|
||||
Console 拒绝码:DOMAIN_NOT_FOUND / DOMAIN_DISABLED / MAILBOX_NOT_FOUND / MAILBOX_DISABLED / RECEIVING_DISABLED。
|
||||
Console 超时、网络失败、5xx 都不能被解释成邮箱不存在,也不能伪造永久拒绝。
|
||||
|
||||
第二阶段(accept 后)存储邮件:
|
||||
|
||||
```text
|
||||
Email Worker -> POST /internal/email/ingest(raw MIME)
|
||||
-> Console:验证 Worker 身份与 recipient -> 解析 MIME -> D1 元数据
|
||||
-> R2 raw MIME / 附件 -> 计算 expires_at -> 创建 Webhook Delivery
|
||||
```
|
||||
|
||||
SMTP 判断与邮件存储完全分离。动态邮箱:创建/删除/禁用邮箱后,下一封邮件的 authorize 立即生效,
|
||||
Worker 不需要保存 mailbox 列表。
|
||||
|
||||
## TTL
|
||||
|
||||
默认保留 1 天:
|
||||
默认保留 1 天,可按 Mailbox 配置:
|
||||
|
||||
```text
|
||||
received_at = 2026-09-30T10:00:00Z
|
||||
expires_at = 2026-10-01T10:00:00Z
|
||||
```
|
||||
|
||||
Retention 可按 Mailbox 配置。
|
||||
|
||||
## Cron
|
||||
|
||||
使用 Cloudflare Cron Trigger,建议每 15 分钟执行一次清理。实际频率根据当前 Cloudflare 限制调整。
|
||||
使用 Cloudflare Cron Trigger(本地每 60s 模拟):
|
||||
|
||||
```text
|
||||
Cron -> 查询 expires_at <= now -> 删除 R2 -> 删除附件 -> 删除 Email
|
||||
+ 过期 nonce / OAuth state 清理 + 过期 webhook delivery 重试
|
||||
```
|
||||
|
||||
删除必须幂等。
|
||||
|
||||
## Webhook
|
||||
|
||||
邮件保存成功后再执行 Webhook。Webhook 失败不能导致邮件重新进入 Node。
|
||||
邮件保存成功后投递(三级解析:邮箱 -> 域名 -> 全局)。Webhook 失败不能导致邮件重新进入接收流程。
|
||||
|
||||
## 异常
|
||||
|
||||
R2 删除失败时保留数据库记录并记录 cleanup error,下一次 Cron 重试。数据库已删除但 R2 删除失败时,需要 orphan cleanup。
|
||||
R2 删除失败时保留数据库记录并记录 cleanup error,下一次 Cron 重试;
|
||||
数据库已删除但 R2 删除失败时,由 orphan cleanup 兜底。ingest 失败不伪造业务拒绝。
|
||||
|
||||
## 数据最小化
|
||||
|
||||
邮件、附件、raw MIME、Webhook Delivery 和临时日志都应有生命周期,不做无限期保存。
|
||||
邮件、附件、raw MIME、Webhook Delivery 和临时日志都应有生命周期,不做无限期保存。
|
||||
|
||||
+22
-11
@@ -1,10 +1,11 @@
|
||||
# Webhook、API 与 MCP
|
||||
|
||||
Webhook、API、MCP 全部属于 Console,Node 不实现这些能力。
|
||||
Webhook、API、MCP 全部属于 Console,Email Worker 不实现这些能力。
|
||||
|
||||
## Webhook
|
||||
## Webhook(三级:邮箱 -> 域名 -> 全局)
|
||||
|
||||
邮件保存成功后创建 delivery。Payload:
|
||||
收到邮件后按 邮箱 -> 域名 -> 全局 的顺序取第一个已配置的 Webhook(无配置则逐级回退),
|
||||
入队 delivery(冻结当时的 url/headers/key 配置)。Payload(docs/07):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -19,27 +20,37 @@ Webhook、API、MCP 全部属于 Console,Node 不实现这些能力。
|
||||
}
|
||||
```
|
||||
|
||||
Webhook 必须签名,建议提供 X-WmailHub-Event、X-WmailHub-Delivery、X-WmailHub-Timestamp、X-WmailHub-Signature。
|
||||
请求头:X-WmailHub-Event、X-WmailHub-Delivery、X-WmailHub-Timestamp、X-WmailHub-Signature
|
||||
(签名 = HMAC-SHA256(key, `${timestamp}.${body}`));配置了 key 时默认携带
|
||||
`Authorization: Bearer <key>`,自定义 Authorization 头优先;无 key 时不携带签名头。
|
||||
|
||||
2xx 为成功;5xx 和 timeout 重试;4xx 按策略处理。使用指数退避和最大尝试次数。
|
||||
2xx 为成功;5xx 和 timeout 按指数退避重试,达到最大尝试次数后置 exhausted。
|
||||
|
||||
## API
|
||||
|
||||
```text
|
||||
/api/v1/nodes
|
||||
/api/v1/accounts
|
||||
/api/v1/domains
|
||||
/api/v1/mailboxes
|
||||
/api/v1/emails
|
||||
/api/v1/webhooks
|
||||
/api/v1/mcp
|
||||
```
|
||||
|
||||
API 属于 Console。
|
||||
API 属于 Console,Bearer API Key(wmh_)认证。
|
||||
|
||||
## MCP
|
||||
|
||||
MCP 允许 Agent 查询邮箱、获取邮件、查询邮件状态、管理邮箱和 Webhook。Node 不暴露 MCP。
|
||||
MCP 允许 Agent 查询邮箱、获取邮件、创建/删除邮箱(list_mailboxes、list_emails、get_email、
|
||||
create_mailbox、delete_mailbox),Bearer MCP Key(wmh_)认证。
|
||||
|
||||
MCP/API 获取邮件时必须检查用户权限、邮件是否过期、mailbox scope 和 API credential scope。
|
||||
MCP/API 获取邮件时必须检查邮件是否过期、mailbox scope 和 API credential scope。
|
||||
|
||||
Webhook endpoint 必须防 SSRF,至少阻止 localhost、private IPv4/IPv6、link-local、metadata endpoint 和 DNS rebinding。
|
||||
## 内部 API
|
||||
|
||||
`/internal/email/authorize` 与 `/internal/email/ingest` 为 Email Worker 专用(docs/04),
|
||||
与 Webhook/API/MCP 无关。
|
||||
|
||||
## SSRF
|
||||
|
||||
Webhook endpoint 必须防 SSRF,至少阻止 localhost、private IPv4/IPv6、link-local、
|
||||
metadata endpoint 和 DNS rebinding。
|
||||
|
||||
+25
-25
@@ -1,47 +1,47 @@
|
||||
# 安全设计
|
||||
# 安全设计(v2,docs/11 §18)
|
||||
|
||||
## 安全边界
|
||||
|
||||
```text
|
||||
Cloudflare Email Worker
|
||||
|
|
||||
| authenticated request
|
||||
v
|
||||
Node
|
||||
|
|
||||
| TLS + HMAC
|
||||
v
|
||||
Console
|
||||
Internet -> Cloudflare Email Routing -> Email Worker
|
||||
|
|
||||
| HTTPS + HMAC + timestamp + nonce + body hash
|
||||
v
|
||||
Console(/internal/email/*)
|
||||
```
|
||||
|
||||
公网不得直接调用 /internal/email/authorize 与 /internal/email/ingest:
|
||||
所有请求必须携带有效的 Worker credential 签名(docs/04)。
|
||||
|
||||
## Cloudflare OAuth
|
||||
|
||||
OAuth Token 加密存储,不进入前端和日志,也不作为 Node credential。
|
||||
OAuth Token 加密存储,不进入前端和日志,也不作为 Worker credential。
|
||||
|
||||
## Node Credential
|
||||
## Worker Credential
|
||||
|
||||
由 Console 自动生成。用户不手工配置。支持撤销和重新 enrollment。
|
||||
由 Console 在接入/部署时自动生成并注入 Worker 脚本,用户不手工配置。
|
||||
支持撤销(立即生效)与轮换(轮换后已配置域名自动重新部署)。
|
||||
|
||||
## 请求签名
|
||||
|
||||
所有 Node -> Console 请求必须签名,签名覆盖 method、path、timestamp、nonce、body hash,防止篡改和 replay。
|
||||
所有 Worker -> Console 请求必须签名,签名覆盖 method、path、timestamp、nonce、body hash,
|
||||
Console 校验 Worker ID、timestamp 窗口、nonce 防重放、body hash、HMAC、credential 状态与
|
||||
账号归属,防止篡改、重放与跨账号访问。
|
||||
|
||||
## Worker -> Node
|
||||
## 隐私与日志
|
||||
|
||||
Worker 使用 Node 专用 ingress secret。Worker 不应拥有 Console credential。
|
||||
- 禁止记录 Cloudflare OAuth token、Worker secret、Webhook key、Authorization header 和完整 MIME;
|
||||
- reject reason 不泄露内部信息(只返回固定文案);
|
||||
- 不向客户端泄露数据库内部错误。
|
||||
|
||||
## 最小权限
|
||||
## 附件与大小限制
|
||||
|
||||
Cloudflare OAuth 只申请业务所需权限。
|
||||
限制单文件大小、单邮件总大小、文件数量和 MIME header 大小,避免资源耗尽。
|
||||
|
||||
## 日志
|
||||
## SSRF
|
||||
|
||||
禁止记录 Cloudflare OAuth token、Node secret、Webhook secret、Authorization header 和完整邮件正文。
|
||||
|
||||
## 附件限制
|
||||
|
||||
限制单文件大小、单邮件总大小、文件数量和 MIME header 大小,避免资源耗尽。
|
||||
Webhook endpoint 校验禁止 localhost、私网、link-local、metadata endpoint 与 DNS rebinding。
|
||||
|
||||
## 数据保留
|
||||
|
||||
默认 Email TTL 为 1 天,过期自动删除。
|
||||
默认 Email TTL 为 1 天,过期自动删除;nonce/oauth state/delivery 均有清理路径。
|
||||
|
||||
@@ -1,33 +1,54 @@
|
||||
# Cloudflare 部署设计
|
||||
# Cloudflare 部署设计(v2,docs/11 §12)
|
||||
|
||||
## 服务
|
||||
|
||||
第一版目标使用 Cloudflare Workers、Email Workers / Email Routing、D1、R2、Cron Triggers、OAuth;Queues 在需要可靠异步投递时再加入,并以当前 Cloudflare 限制为准。
|
||||
使用 Cloudflare Workers、Email Workers / Email Routing、D1、R2、Cron Triggers、OAuth;
|
||||
Queues 在需要可靠异步投递时再加入,并以当前 Cloudflare 限制为准。
|
||||
|
||||
## Console
|
||||
|
||||
Console 本身部署在 Cloudflare Workers:
|
||||
Console 本身部署在 Cloudflare Workers:
|
||||
|
||||
```text
|
||||
Worker
|
||||
+-- Frontend Assets
|
||||
+-- Management API
|
||||
+-- /internal/email/authorize + /internal/email/ingest(Email Worker 专用)
|
||||
+-- OAuth Callback
|
||||
+-- Cron Handler
|
||||
```
|
||||
|
||||
## Node
|
||||
部署步骤:`bunx wrangler d1 create wmailhub` -> `wrangler r2 bucket create` ->
|
||||
`wrangler d1 execute wmailhub --remote --file=./migrations/0001_init.sql`(以及 0002/0003)->
|
||||
`wrangler secret put SESSION_SECRET` -> 配置 CF_OAUTH_CLIENT_ID/SECRET/REDIRECT_URI vars。
|
||||
PUBLIC_BASE_URL 即 Email Worker 里的 CONSOLE_URL。
|
||||
|
||||
Node 是独立纯后端程序,无前端。可以部署在用户服务器、云服务器或其他可访问 Console 的环境,但 Cloudflare Email Worker 必须能访问 Node ingress。
|
||||
## Email Routing 与 catch-all Worker(动态邮箱)
|
||||
|
||||
动态邮箱场景把域名邮件通过 catch-all 规则交给 WmailHub Email Worker:
|
||||
|
||||
```text
|
||||
*@example.com
|
||||
|
|
||||
v
|
||||
WmailHub Email Worker -> Console(authorize)
|
||||
+-- accept -> ingest
|
||||
+-- reject -> message.setReject()
|
||||
```
|
||||
|
||||
这样即使 unknown@example.com 在 Console 中不存在,也会进入 Worker,
|
||||
由 Console 判断后执行 `message.setReject()`。如果只为现有邮箱创建 literal routing,
|
||||
不存在的地址可能根本不会进入 Worker,WmailHub 无法完成 SMTP 阶段拒绝。
|
||||
Console 的「域名 -> 设置 Email Workers」会自动完成:开启 Email Routing + catch-all 指向 Worker。
|
||||
|
||||
## Email Worker
|
||||
|
||||
Email Worker 只接收 Email Event 并安全转发到 Node,不保存业务数据库。
|
||||
账号级一个 Worker(名字 wmailhub-email-<account_id>),脚本由 Console 自动上传,
|
||||
内含 CONSOLE_URL / WORKER_ID / WORKER_SECRET;只做 authorize + ingest + setReject,
|
||||
不保存业务数据。
|
||||
|
||||
## R2
|
||||
|
||||
用于 raw MIME 和 attachments:
|
||||
|
||||
```text
|
||||
emails/{yyyy}/{mm}/{dd}/{email_id}/raw.eml
|
||||
emails/{yyyy}/{mm}/{dd}/{email_id}/attachments/{attachment_id}
|
||||
@@ -35,8 +56,11 @@ emails/{yyyy}/{mm}/{dd}/{email_id}/attachments/{attachment_id}
|
||||
|
||||
## D1
|
||||
|
||||
保存 nodes、domains、mailboxes、emails、webhooks、webhook deliveries、oauth connections。
|
||||
保存 cloudflare_accounts、domains、mailboxes、emails、email_attachments、
|
||||
webhook_deliveries、oauth_connections、worker_credentials、users、settings、api_credentials、
|
||||
nonces、oauth_states。
|
||||
|
||||
## Cron
|
||||
|
||||
至少实现 cleanup job,建议每 15 分钟一次,具体以当前 Cloudflare 限制调整。
|
||||
至少实现 cleanup job(过期邮件 + orphan + nonce/state 清理),建议每 15 分钟一次;
|
||||
webhook 重试每 1 分钟,具体以当前 Cloudflare 限制调整。
|
||||
|
||||
@@ -1,61 +1,56 @@
|
||||
# ZCODE 实施计划
|
||||
# WmailHub 实施计划(v2,docs/11 §20)
|
||||
|
||||
## Phase 1 项目骨架
|
||||
## Phase 1 Console 重构(数据模型)
|
||||
|
||||
```text
|
||||
WmailHub/
|
||||
console/
|
||||
node/
|
||||
worker/
|
||||
docs/
|
||||
```
|
||||
Cloudflare Account(cloudflare_accounts)、Domain、Mailbox、WorkerCredential;
|
||||
迁移 0003 移除 nodes / node_credentials,emails/domains 归属改为 account_id。
|
||||
|
||||
Console、Node、Worker 必须边界清晰,共享协议独立。
|
||||
## Phase 2 Authorization API
|
||||
|
||||
## Phase 2 Console
|
||||
POST /internal/email/authorize:recipient/sender -> accept | reject(code+reason) | temporary_error;
|
||||
拒绝码 DOMAIN_NOT_FOUND / DOMAIN_DISABLED / MAILBOX_NOT_FOUND / MAILBOX_DISABLED / RECEIVING_DISABLED。
|
||||
|
||||
实现 Console Web、Management API、数据库、认证、Node CRUD、Domain CRUD、Mailbox CRUD、Email CRUD。
|
||||
## Phase 3 Email Worker
|
||||
|
||||
## Phase 3 Cloudflare OAuth
|
||||
email handler + HMAC 签名 + authorize + setReject + ingest(共享逻辑见
|
||||
shared/src/email-worker-template.ts 与 worker/src)。
|
||||
|
||||
实现 OAuth start、callback、state、token exchange、Account/Zone discovery、加密 token storage。
|
||||
## Phase 4 Mail Ingest API
|
||||
|
||||
## Phase 4 自动 Node 安装
|
||||
POST /internal/email/ingest:message/rfc822 原文直传(不转 JSON 字符串),
|
||||
复验 recipient(动态邮箱),只允许 accept 后调用。
|
||||
|
||||
Add Node 后完成:OAuth、Node identity、Node credential、Email Worker、Worker -> Node、Email Routing、Domain、Mailbox、Node heartbeat。正常流程禁止要求用户复制安装命令。
|
||||
## Phase 5 Cloudflare 自动配置
|
||||
|
||||
## Phase 5 Node
|
||||
Connect Cloudflare Account:OAuth -> Account/Zone 发现 -> 域名同步 ->
|
||||
Email Worker 部署(注入 CONSOLE_URL/WORKER_ID/WORKER_SECRET)-> WorkerCredential 签发;
|
||||
逐域名:Email Routing + catch-all 规则。
|
||||
|
||||
实现 bootstrap、secure config、Email Worker ingress、Console egress、HMAC signing、heartbeat、有限 retry、graceful shutdown、structured logging。
|
||||
## Phase 6 邮件存储
|
||||
|
||||
Node 不实现业务路由。
|
||||
D1 元数据 + R2 raw MIME/附件 + MIME 解析(降级入库)+ 大小/数量限额。
|
||||
|
||||
## Phase 6 Email Pipeline
|
||||
## Phase 7 Webhook / REST API / MCP / TTL
|
||||
|
||||
```text
|
||||
Cloudflare -> Worker -> Node -> Console -> D1/R2
|
||||
```
|
||||
三级 Webhook(邮箱 -> 域名 -> 全局,冻结投递配置)、Bearer API/MCP Key、MCP 工具、
|
||||
Cron 清理(过期邮件 + orphan + nonce/state)+ Webhook 重试。
|
||||
|
||||
测试 plain text、HTML、MIME、attachment、多收件人、大正文和 malformed mail。
|
||||
## Phase 8 完整测试
|
||||
|
||||
## Phase 7 Webhook
|
||||
1. 存在邮箱 -> 接收成功;
|
||||
2. 不存在邮箱 -> message.setReject()(MAILBOX_NOT_FOUND);
|
||||
3. 禁用邮箱 -> setReject(MAILBOX_DISABLED);
|
||||
4. 禁用域名 -> setReject(DOMAIN_DISABLED);
|
||||
5. Console 超时 -> temporary_error,不能变成 mailbox not found;
|
||||
6. Console 5xx -> temporary_error;
|
||||
7. ingest 失败 -> 不伪造业务拒绝;
|
||||
8. Worker credential revoke/rotate -> Worker 无法访问 Console;
|
||||
9. 新建邮箱后测试立即接收;
|
||||
10. 删除邮箱后测试立即拒绝;
|
||||
11. 多域名并发测试;
|
||||
12. catch-all 地址测试。
|
||||
|
||||
实现 CRUD、secret、signing、delivery、retry、timeout、失败状态和日志。
|
||||
## 最终验收
|
||||
|
||||
## Phase 8 TTL
|
||||
|
||||
实现 retention configuration、expires_at、Cron cleanup、R2 cleanup 和 orphan cleanup。默认 1 day。
|
||||
|
||||
## Phase 9 API / MCP
|
||||
|
||||
实现 Console API,并提供 list_mailboxes、list_emails、get_email、create_mailbox、delete_mailbox、list_webhooks 等 MCP 工具。权限必须基于 Console credential。
|
||||
|
||||
## Phase 10 安全测试
|
||||
|
||||
测试 OAuth CSRF、credential revoke、replay attack、伪造 Node 请求、body 篡改、过期 timestamp、Webhook SSRF、未授权 mailbox、未授权 Cloudflare Account、超大邮件和超大附件。
|
||||
|
||||
## Phase 11 验收
|
||||
|
||||
必须完成:Cloudflare OAuth -> 自动 Node 安装 -> Domain 配置 -> Mailbox 创建 -> 真实 Email -> Email Worker -> Node -> Console -> Database/Webhook/API/MCP -> TTL 自动删除。
|
||||
|
||||
如果正常流程要求用户手工登录服务器执行安装命令,则不符合设计目标。
|
||||
运行架构必须是 `Internet -> Cloudflare Email Routing -> Email Worker -> Console(D1/R2/Webhook/API/MCP)`;
|
||||
不得存在 `Email Worker -> Node -> Console`。
|
||||
|
||||
@@ -0,0 +1,676 @@
|
||||
|
||||
# WmailHub 邮件接收架构改造方案
|
||||
|
||||
> 版本:v2
|
||||
> 更新时间:2026-09-30
|
||||
> 项目:/home/bigcode/data/dev/bun/WmailHub
|
||||
|
||||
## 1. 最终架构
|
||||
|
||||
WmailHub 第一版正式采用两层邮件接收架构:
|
||||
|
||||
Internet
|
||||
|
|
||||
v
|
||||
Cloudflare Email Routing
|
||||
|
|
||||
v
|
||||
Email Worker
|
||||
|
|
||||
| HTTPS + HMAC
|
||||
v
|
||||
WmailHub Console
|
||||
|
||||
删除旧方案中的 Node。
|
||||
|
||||
旧方案:
|
||||
|
||||
Cloudflare -> Email Worker -> Node -> Console
|
||||
|
||||
新方案:
|
||||
|
||||
Cloudflare -> Email Worker -> Console
|
||||
|
||||
Cloudflare Email Workers 官方 API 支持 email handler、message.from、message.to、message.raw 和 message.setReject();Workers 也可以通过 fetch() 调用外部 HTTPS API。因此 Node 在邮件链路中没有必要。
|
||||
|
||||
## 2. 核心原则
|
||||
|
||||
1. 不自建 SMTP、IMAP、POP3。
|
||||
2. 不部署邮件转发 Node。
|
||||
3. Email Worker 是 SMTP 入口。
|
||||
4. Console 是唯一业务控制中心。
|
||||
5. Worker 不保存邮箱业务数据。
|
||||
6. Worker 在 SMTP 接收阶段查询 Console。
|
||||
7. Console 明确允许时,Worker 接收邮件。
|
||||
8. Console 明确拒绝时,Worker 调用 message.setReject()。
|
||||
9. Console 超时或 5xx 不能被解释成邮箱不存在。
|
||||
10. 邮箱、域名、启用状态全部由 Console 管理。
|
||||
|
||||
## 3. 邮件接收流程
|
||||
|
||||
邮件进入 Email Worker 后,第一步不是保存邮件,而是询问 Console:
|
||||
|
||||
POST /internal/email/authorize
|
||||
|
||||
请求:
|
||||
|
||||
{
|
||||
"recipient": "test@example.com",
|
||||
"sender": "sender@example.net"
|
||||
}
|
||||
|
||||
Console 返回三种明确状态。
|
||||
|
||||
### ACCEPT
|
||||
|
||||
{
|
||||
"decision": "accept"
|
||||
}
|
||||
|
||||
Worker 继续接收邮件,然后提交:
|
||||
|
||||
POST /internal/email/ingest
|
||||
|
||||
### REJECT
|
||||
|
||||
例如邮箱不存在:
|
||||
|
||||
{
|
||||
"decision": "reject",
|
||||
"code": "MAILBOX_NOT_FOUND",
|
||||
"reason": "Mailbox does not exist"
|
||||
}
|
||||
|
||||
Worker:
|
||||
|
||||
message.setReject("Mailbox does not exist");
|
||||
return;
|
||||
|
||||
这是 SMTP 阶段的永久拒绝。
|
||||
|
||||
### TEMPORARY_ERROR
|
||||
|
||||
例如 Console 超时、网络失败、5xx:
|
||||
|
||||
{
|
||||
"decision": "temporary_error"
|
||||
}
|
||||
|
||||
不能转换成 MAILBOX_NOT_FOUND,也不能伪造永久拒绝。
|
||||
|
||||
第一版采用安全失败策略:让 Worker 处理异常,由 Cloudflare 的邮件处理机制负责后续失败行为;后续根据实际测试补充重试策略。
|
||||
|
||||
## 4. 为什么删除 Node
|
||||
|
||||
Node 原来的职责只是:
|
||||
|
||||
接收邮件 -> 转发邮件
|
||||
|
||||
这会额外产生:
|
||||
|
||||
- 一次网络跳转
|
||||
- 一套部署
|
||||
- 一套认证
|
||||
- 一个故障点
|
||||
- 一套重试
|
||||
- 一套日志
|
||||
- 一套版本管理
|
||||
|
||||
Email Worker 已经能够:
|
||||
|
||||
- 接收邮件
|
||||
- 读取 envelope sender / recipient
|
||||
- 执行业务前置判断
|
||||
- 调用 HTTPS API
|
||||
- reject
|
||||
- 将原始邮件交给 Console
|
||||
|
||||
因此 Node 没有独立价值。
|
||||
|
||||
## 5. Email Worker 职责
|
||||
|
||||
Worker 只负责:
|
||||
|
||||
1. 接收 Cloudflare Email Routing 邮件。
|
||||
2. 读取 message.from。
|
||||
3. 读取 message.to。
|
||||
4. 调用 Console authorize API。
|
||||
5. 明确拒绝时执行 message.setReject()。
|
||||
6. 明确允许时调用 Console ingest API。
|
||||
7. 处理内部异常。
|
||||
|
||||
Worker 不负责:
|
||||
|
||||
- 创建邮箱
|
||||
- 删除邮箱
|
||||
- 域名管理
|
||||
- 用户权限
|
||||
- Webhook
|
||||
- API
|
||||
- MCP
|
||||
- D1 业务查询
|
||||
- R2 业务存储
|
||||
- TTL
|
||||
- 邮箱业务规则
|
||||
|
||||
## 6. Console 职责
|
||||
|
||||
Console 是唯一业务控制中心。
|
||||
|
||||
负责:
|
||||
|
||||
- Cloudflare OAuth
|
||||
- Cloudflare Account
|
||||
- Domain
|
||||
- Mailbox
|
||||
- 邮箱启用/禁用
|
||||
- Email Storage
|
||||
- Webhook
|
||||
- REST API
|
||||
- MCP
|
||||
- TTL Cleanup
|
||||
- Cloudflare Email Routing 配置
|
||||
- Email Worker 部署
|
||||
- Worker Credential
|
||||
|
||||
Console 是“是否允许接收邮件”的唯一事实来源。
|
||||
|
||||
## 7. Authorization API
|
||||
|
||||
新增:
|
||||
|
||||
POST /internal/email/authorize
|
||||
|
||||
请求:
|
||||
|
||||
{
|
||||
"recipient": "test@example.com",
|
||||
"sender": "sender@example.net"
|
||||
}
|
||||
|
||||
可选:
|
||||
|
||||
{
|
||||
"recipient": "test@example.com",
|
||||
"sender": "sender@example.net",
|
||||
"message_id": "<abc@example.net>"
|
||||
}
|
||||
|
||||
响应只能使用明确 decision:
|
||||
|
||||
accept
|
||||
reject
|
||||
temporary_error
|
||||
|
||||
reject 的 code 至少包括:
|
||||
|
||||
DOMAIN_NOT_FOUND
|
||||
DOMAIN_DISABLED
|
||||
MAILBOX_NOT_FOUND
|
||||
MAILBOX_DISABLED
|
||||
RECEIVING_DISABLED
|
||||
|
||||
不要只返回 exists=true/false,因为未来拒绝条件不只是邮箱是否存在。
|
||||
|
||||
## 8. Worker -> Console 安全协议
|
||||
|
||||
Authorization 和 Ingest 都必须是内部 API,不能匿名访问。
|
||||
|
||||
使用:
|
||||
|
||||
HTTPS
|
||||
+
|
||||
Worker ID
|
||||
+
|
||||
timestamp
|
||||
+
|
||||
nonce
|
||||
+
|
||||
body SHA256
|
||||
+
|
||||
HMAC-SHA256
|
||||
|
||||
请求头:
|
||||
|
||||
Authorization: WmailHub-Worker <worker_id>
|
||||
X-WmailHub-Timestamp: <unix_seconds>
|
||||
X-WmailHub-Nonce: <random>
|
||||
X-WmailHub-Body-SHA256: <sha256>
|
||||
X-WmailHub-Signature: <hmac>
|
||||
X-WmailHub-Protocol: 1
|
||||
|
||||
Canonical String:
|
||||
|
||||
METHOD + "\n" +
|
||||
PATH + "\n" +
|
||||
TIMESTAMP + "\n" +
|
||||
NONCE + "\n" +
|
||||
BODY_SHA256
|
||||
|
||||
Console 必须:
|
||||
|
||||
1. 校验 Worker ID。
|
||||
2. 校验 timestamp。
|
||||
3. 防止 nonce 重放。
|
||||
4. 校验 body hash。
|
||||
5. 校验 HMAC。
|
||||
6. 校验 Worker 状态。
|
||||
7. 校验 Worker 所属 Cloudflare Account。
|
||||
|
||||
## 9. Mail Ingest API
|
||||
|
||||
新增:
|
||||
|
||||
POST /internal/email/ingest
|
||||
|
||||
只有 authorize 返回 accept 后才能调用。
|
||||
|
||||
建议:
|
||||
|
||||
Content-Type: message/rfc822
|
||||
|
||||
HTTP body 直接传递原始 MIME,不要把整个 MIME 转成 JSON 字符串。
|
||||
|
||||
Console 收到后:
|
||||
|
||||
1. 验证 Worker 身份。
|
||||
2. 验证 recipient。
|
||||
3. 创建 Email metadata。
|
||||
4. 将 raw MIME 写入 R2。
|
||||
5. 保存附件信息。
|
||||
6. 触发 Webhook。
|
||||
7. 返回成功。
|
||||
|
||||
## 10. 两阶段模型
|
||||
|
||||
第一阶段:
|
||||
|
||||
Email Worker
|
||||
|
|
||||
v
|
||||
/authorize
|
||||
|
|
||||
+-- accept
|
||||
|
|
||||
+-- reject
|
||||
|
|
||||
+-- temporary_error
|
||||
|
||||
这一阶段只回答:
|
||||
|
||||
“这封邮件是否应该由 WmailHub 接收?”
|
||||
|
||||
第二阶段:
|
||||
|
||||
Email Worker
|
||||
|
|
||||
v
|
||||
/ingest
|
||||
|
|
||||
+-- D1
|
||||
+-- R2
|
||||
+-- Webhook
|
||||
|
||||
这样 SMTP 判断和邮件存储完全分离。
|
||||
|
||||
## 11. 动态邮箱
|
||||
|
||||
Console 创建:
|
||||
|
||||
support@example.com
|
||||
|
||||
下一封邮件:
|
||||
|
||||
Worker
|
||||
-> authorize
|
||||
-> accept
|
||||
-> ingest
|
||||
|
||||
删除或禁用后:
|
||||
|
||||
Worker
|
||||
-> authorize
|
||||
-> reject
|
||||
-> message.setReject()
|
||||
|
||||
Worker 不需要保存 mailbox list,也不需要 Node 同步配置。
|
||||
|
||||
## 12. Cloudflare Email Routing
|
||||
|
||||
动态邮箱场景推荐把域名邮件通过 catch-all Worker 交给 WmailHub。
|
||||
|
||||
例如:
|
||||
|
||||
*@example.com
|
||||
|
|
||||
v
|
||||
WmailHub Email Worker
|
||||
|
||||
这样即使:
|
||||
|
||||
unknown@example.com
|
||||
|
||||
在 Console 中不存在,也能进入 Worker,由 Console 判断后执行 message.setReject()。
|
||||
|
||||
如果只为现有邮箱创建 literal routing,那么不存在的地址可能根本不会进入 Worker,因此无法由 WmailHub 自己完成 SMTP reject。
|
||||
|
||||
## 13. 数据模型
|
||||
|
||||
删除 Node 作为邮件链路中的核心模型。
|
||||
|
||||
保留:
|
||||
|
||||
### CloudflareAccount
|
||||
|
||||
- id
|
||||
- account_id
|
||||
- oauth_connection_id
|
||||
- status
|
||||
|
||||
### Domain
|
||||
|
||||
- id
|
||||
- cloudflare_account_id
|
||||
- name
|
||||
- status
|
||||
|
||||
### Mailbox
|
||||
|
||||
- id
|
||||
- domain_id
|
||||
- address
|
||||
- status
|
||||
- created_at
|
||||
- updated_at
|
||||
|
||||
### Email
|
||||
|
||||
- id
|
||||
- mailbox_id
|
||||
- message_id
|
||||
- sender
|
||||
- recipient
|
||||
- subject
|
||||
- received_at
|
||||
- expires_at
|
||||
- raw_object_key
|
||||
|
||||
### EmailAttachment
|
||||
|
||||
保留。
|
||||
|
||||
### Webhook
|
||||
|
||||
保留。
|
||||
|
||||
### WebhookDelivery
|
||||
|
||||
保留。
|
||||
|
||||
### WorkerCredential
|
||||
|
||||
用于 Worker 与 Console 内部认证:
|
||||
|
||||
- id
|
||||
- cloudflare_account_id
|
||||
- worker_id
|
||||
- secret
|
||||
- status
|
||||
- created_at
|
||||
- revoked_at
|
||||
|
||||
## 14. Cloudflare OAuth
|
||||
|
||||
旧的“Add Node”概念删除。
|
||||
|
||||
改为:
|
||||
|
||||
Connect Cloudflare Account
|
||||
|
||||
流程:
|
||||
|
||||
Console
|
||||
|
|
||||
v
|
||||
Cloudflare OAuth
|
||||
|
|
||||
v
|
||||
用户授权 Account
|
||||
|
|
||||
v
|
||||
Console
|
||||
|
|
||||
+-- 保存 Account
|
||||
+-- 配置 Email Routing
|
||||
+-- 部署 Email Worker
|
||||
+-- 配置 Worker Secret
|
||||
+-- 配置 Domain
|
||||
|
||||
用户不需要执行安装命令。
|
||||
|
||||
## 15. 故障策略
|
||||
|
||||
### Console 正常
|
||||
|
||||
authorize -> accept/reject
|
||||
|
||||
### Console 明确 reject
|
||||
|
||||
message.setReject()
|
||||
|
||||
### Console 5xx
|
||||
|
||||
不能当作邮箱不存在。
|
||||
|
||||
### Console timeout
|
||||
|
||||
不能当作邮箱不存在。
|
||||
|
||||
### Ingest 失败
|
||||
|
||||
不能伪造业务 reject。
|
||||
|
||||
必须记录处理失败,并按照 Cloudflare Email Worker 的实际投递/重试行为处理。
|
||||
|
||||
## 16. 不实现 NDR
|
||||
|
||||
第一版明确不采用:
|
||||
|
||||
250 OK
|
||||
|
|
||||
v
|
||||
异步发现邮箱不存在
|
||||
|
|
||||
v
|
||||
WmailHub 自己生成 NDR
|
||||
|
||||
原因:
|
||||
|
||||
- 增加 SMTP outbound
|
||||
- 增加 DSN/NDR 实现
|
||||
- 增加 SPF/DKIM/DMARC 发送问题
|
||||
- 增加 bounce loop 风险
|
||||
- 增加 backscatter 风险
|
||||
- 增加系统复杂度
|
||||
|
||||
本版本直接使用 Cloudflare Email Worker 的 message.setReject()。
|
||||
|
||||
## 17. 性能
|
||||
|
||||
第一版每封邮件:
|
||||
|
||||
1 次 /authorize
|
||||
+
|
||||
1 次 /ingest
|
||||
|
||||
这是有意选择的。
|
||||
|
||||
暂不加入:
|
||||
|
||||
- KV mailbox cache
|
||||
- Durable Object
|
||||
- mailbox synchronization
|
||||
- Node queue
|
||||
- 本地 mailbox database
|
||||
|
||||
未来如果 authorize 成为瓶颈,再考虑缓存。
|
||||
|
||||
但缓存必须接受邮箱创建、删除和停用存在传播延迟的可能性。
|
||||
|
||||
## 18. 安全
|
||||
|
||||
必须防止公网直接调用:
|
||||
|
||||
/internal/email/authorize
|
||||
/internal/email/ingest
|
||||
|
||||
必须:
|
||||
|
||||
- HTTPS
|
||||
- HMAC
|
||||
- timestamp
|
||||
- nonce
|
||||
- body hash
|
||||
- Worker credential revoke
|
||||
- 防重放
|
||||
- 不记录完整 MIME
|
||||
- 不泄露数据库内部错误
|
||||
- reject reason 不暴露敏感信息
|
||||
|
||||
## 19. 文档同步修改
|
||||
|
||||
以下旧文档必须同步修改:
|
||||
|
||||
01_ARCHITECTURE.md
|
||||
02_CLOUDFLARE_OAUTH.md
|
||||
03_NODE.md
|
||||
04_NODE_PROTOCOL.md
|
||||
05_DATA_MODEL.md
|
||||
06_EMAIL_LIFECYCLE.md
|
||||
07_WEBHOOK_API_MCP.md
|
||||
08_SECURITY.md
|
||||
09_CLOUDFLARE_DEPLOYMENT.md
|
||||
10_IMPLEMENTATION_PLAN.md
|
||||
|
||||
具体:
|
||||
|
||||
- 03_NODE.md:删除 Node 邮件转发设计。
|
||||
- 04_NODE_PROTOCOL.md:改成 Worker-Console Protocol。
|
||||
- 02_CLOUDFLARE_OAUTH.md:Add Node 改成 Connect Cloudflare Account + 自动部署 Worker。
|
||||
- 05_DATA_MODEL.md:删除 Node 邮件转发相关模型。
|
||||
- 06_EMAIL_LIFECYCLE.md:改成 Worker -> Console。
|
||||
- 09_CLOUDFLARE_DEPLOYMENT.md:增加 catch-all Worker routing。
|
||||
- 10_IMPLEMENTATION_PLAN.md:重新排列实施阶段。
|
||||
|
||||
## 20. 实施顺序
|
||||
|
||||
### Phase 1
|
||||
|
||||
重构 Console:
|
||||
|
||||
- Cloudflare Account
|
||||
- Domain
|
||||
- Mailbox
|
||||
- WorkerCredential
|
||||
|
||||
### Phase 2
|
||||
|
||||
实现:
|
||||
|
||||
POST /internal/email/authorize
|
||||
|
||||
### Phase 3
|
||||
|
||||
实现 Email Worker:
|
||||
|
||||
- email handler
|
||||
- HMAC
|
||||
- authorize
|
||||
- setReject
|
||||
- ingest
|
||||
|
||||
### Phase 4
|
||||
|
||||
实现:
|
||||
|
||||
POST /internal/email/ingest
|
||||
|
||||
### Phase 5
|
||||
|
||||
Cloudflare 自动配置:
|
||||
|
||||
- Email Routing
|
||||
- catch-all Worker
|
||||
- Worker deployment
|
||||
- Worker secrets
|
||||
|
||||
### Phase 6
|
||||
|
||||
邮件存储:
|
||||
|
||||
- D1
|
||||
- R2
|
||||
- MIME
|
||||
- Attachment
|
||||
|
||||
### Phase 7
|
||||
|
||||
- Webhook
|
||||
- REST API
|
||||
- MCP
|
||||
- TTL cleanup
|
||||
|
||||
### Phase 8
|
||||
|
||||
完整测试:
|
||||
|
||||
1. 存在邮箱 -> 接收成功。
|
||||
2. 不存在邮箱 -> message.setReject()。
|
||||
3. 禁用邮箱 -> message.setReject()。
|
||||
4. 禁用域名 -> message.setReject()。
|
||||
5. Console timeout -> 不能变成 mailbox not found。
|
||||
6. Console 5xx -> 不能变成 mailbox not found。
|
||||
7. ingest 失败 -> 不伪造业务拒绝。
|
||||
8. Worker credential revoke -> Worker 无法访问 Console。
|
||||
9. 新建邮箱后测试立即接收。
|
||||
10. 删除邮箱后测试立即拒绝。
|
||||
11. 多域名并发测试。
|
||||
12. catch-all 地址测试。
|
||||
|
||||
## 21. 最终验收标准
|
||||
|
||||
最终运行架构必须是:
|
||||
|
||||
Internet
|
||||
|
|
||||
v
|
||||
Cloudflare Email Routing
|
||||
|
|
||||
v
|
||||
Email Worker
|
||||
|
|
||||
v
|
||||
WmailHub Console
|
||||
|
|
||||
+-- D1
|
||||
+-- R2
|
||||
+-- Webhook
|
||||
+-- API
|
||||
+-- MCP
|
||||
|
||||
不得存在:
|
||||
|
||||
Email Worker -> Node -> Console
|
||||
|
||||
WmailHub 第一版的邮件入口、SMTP 决策和业务控制关系正式确定为:
|
||||
|
||||
Cloudflare
|
||||
-> Email Worker
|
||||
-> Console
|
||||
|
||||
其中:
|
||||
|
||||
- Worker = 邮件入口 + SMTP reject 执行者
|
||||
- Console = 邮箱规则 + 邮件业务控制中心
|
||||
- D1/R2 = Console 的数据层
|
||||
- Node = 从 WmailHub 邮件架构中移除
|
||||
Reference in New Issue
Block a user