Cloud Mail 部署
TL;DR
基于 maillab/cloud-mail Fork 到 miao-Q777,部署到 CF Workers。Web UI 在 mail.gottenergy.com,GitHub Actions 自动部署。
现状:✅ Worker 已部署、Web UI 正常、admin@gottenergy.com 超管已注册、Mailgun Webhook 适配器已开发且 curl 验证通过。收件方案最终定为 Google Workspace 做总 MX,未知用户转发给 CF Email Routing(Split Delivery)。Mailgun Webhook 作为备选。等待老师提供 Google Workspace 管理权限后完成最后一步配置。
🎯 背景:为什么需要这个
- 需求:为 Gott Energy 项目提供免费企业邮箱,支持多用户、附件收发
- 卡点:根域名 gottenergy.com 的 MX 被 Google Workspace 占用,CF Email Routing 无法激活(报错
Existing non-Cloudflare MX records conflict with Email Routing) - 约束:不能删 Google MX(老师在用),必须共存
✅ 前置条件
- Cloudflare 账号(Gott Energy)
- 域名 gottenergy.com 在 CF 托管
- GitHub Fork miao-Q777/cloud-mail
- CF API Token(Workers Scripts + D1 + KV + R2 Edit + DNS Read)
- Worker 绑定 mail.gottenergy.com(CNAME + Worker Route)
- Google Workspace 管理权限(等待老师提供)
🏗️ 最终架构(Split Delivery)
外部发件人
│
▼
Google MX (aspmx.l.google.com) ← gottenergy.com 唯一 MX
│
├── joel@ / heidi@ → Google Gmail 自留
│
└── 未知用户 / 指定用户 → Google 转发规则
│
▼
mx1.cloudflare.net (port 25, TLS)
│
▼
CF Email Routing Catch-all
│
▼
cloud-mail Worker email() 事件
│
▼
D1 (收件箱) + R2 (附件)
Google 配置方法:Workspace 管理控制台 → Gmail → 路由 → Hosts(添加 mx1/mx2.cloudflare.net)→ Routing Rule(未识别用户 → 更改路由到 Cloudflare 主机)
🛠️ 部署步骤
第 1 步:Fork 仓库
Fork https://github.com/maillab/cloud-mail → miao-Q777,勾选 “Copy the main branch only”。
第 2 步:创建 CF 资源
CF Dashboard → Storage & Databases:
- D1: cloud-mail-db(Automatic)
- KV: cloud-mail-kv
- R2: cloud-mail-r2(Automatic, Standard)
第 3 步:API Token
权限:D1:Edit, R2:Edit, KV:Edit, Workers Scripts:Edit, DNS:Read
第 4 步:JWT_SECRET
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"第 5 步:GitHub Secrets
| Name | Secret |
|---|---|
| CLOUDFLARE_API_TOKEN | CF API Token |
| CLOUDFLARE_ACCOUNT_ID | 6d048f06c7a9c6a1737ebc019d8ad549 |
| D1_DATABASE_ID | cloud-mail-db UUID |
| KV_NAMESPACE_ID | cloud-mail-kv UUID |
| R2_BUCKET_NAME | cloud-mail-r2 |
| DOMAIN | [“gottenergy.com”, “mail.gottenergy.com”] |
| ADMIN | admin@gottenergy.com |
| JWT_SECRET | 随机字符串 |
第 6 步:GitHub Actions 部署
Actions → Deploy → Run workflow。
第 7 步:绑定域名
- DNS CNAME: mail → cloud-mail.gott-energy.workers.dev(橙云)
- Worker Route: mail.gottenergy.com/* → cloud-mail
第 8 步:初始化数据库
https://mail.gottenergy.com/api/init/JWT_SECRET → success
第 9 步:注册管理员
https://mail.gottenergy.com → 注册 admin@gottenergy.com(首个=超管)
🧪 Mailgun Webhook 适配器(备选方案,已开发验证)
因为 CF Email Routing 被 Google MX 卡住,我们探索了用 Mailgun 做收信中转。虽然最终选了 Split Delivery 方案,但这段开发验证了 Cloud Mail 的核心收件链路。
改动内容
Fork 仓库 miao-Q777/cloud-mail,两个文件:
- 新建
mail-worker/src/api/mailgun-api.js:Hono 路由,POST/webhooks/mailgun,解析 Mailgun form-data → 调emailService.receive()→ D1 - 修改
mail-worker/src/hono/webs.js:加一行import '../api/mailgun-api'
关键发现
emailService.receive()不依赖 CF 专有格式,只接一个普通 params 对象- 原生
email()入口和 Mailgun Webhook 共享同一套 service 层 - security.js 有
exclude白名单,/webhooks已在其中,路由可复用公开路径
curl 验证结果
curl -X POST https://mail.gottenergy.com/api/webhooks/mailgun \
-F sender=test@gmail.com \
-F recipient=admin@mail.gottenergy.com \
-F subject="curl test" \
-F body-plain="Hello from curl"
# → {"success":true}
# → Cloud Mail UI 收件箱可见邮件Mailgun 免费额度
- 1 条 Inbound Route(免费)、100 封/天、无需绑卡
- mail.gottenergy.com 的 Mailgun MX 记录已存在(2026-05 创建),无需改 DNS
↩️ 回滚 / 卸载
- 删除 Worker: CF Dashboard → cloud-mail → Delete
- 删除资源: D1/KV/R2 对应删除
- 删除 Worker Route 和 DNS CNAME
- 删除 API Token
- 回滚验证: mail.gottenergy.com 返回 404/522
🔍 验证方法
- DB 初始化:
curl https://mail.gottenergy.com/api/init/JWT_SECRET→ success - Web UI: 打开
https://mail.gottenergy.com→ 登录页 - curl 模拟收件: POST
/api/webhooks/mailgun→{"success":true}→ UI 可见
💡 总结经验
- 最大教训: 域名上可能有隐藏 MX 记录(Google/Mailgun),DNS 面板不完整,API 才可靠
- 如果再来一次: 先
curl CF API /dns_records?type=MX看清全貌再动手 - 本质: 多套邮件系统 MX 互斥是 SMTP 协议决定的,跟 CF 无关
- 意外收获:
emailService.receive()不绑 CF 专有格式,为 Mailgun 适配打下了基础
🕳️ 踩过的坑
坑 1: 域名 404 但 Worker 正常
现象:
https://gottenergy.com/api/init/...返回 404 根因: Worker Route 设在 gottenergy.com 但根域 A 记录指 Hostinger VPS,CF Tunnel 劫持 HTTP 解决: 改用子域 mail.gottenergy.com,Worker Route + CNAME 双绑
坑 2: 删 CNAME 导致 Web UI 无法访问
现象: 删掉 mail.gottenergy.com CNAME 后页面打不开 根因: CNAME 是 HTTP 流量到 CF 边缘的入口,Worker Route 依赖它。CF 有 CNAME flattening,MX 和 CNAME 可共存 解决: 恢复 CNAME
坑 3: Mailgun Webhook 返回 401
现象:
curl POST /api/mailgun-webhook→{"code":401,"message":"身份认证失效"}根因: security.js 的 exclude 白名单里没有/mailgun-webhook,被 JWT 中间件拦截 解决: 改成/webhooks/mailgun(/webhooks已在 exclude 中)
坑 4: git push 403 用户不匹配
现象:
Permission to miao-Q777/cloud-mail.git denied to miaoQ777根因: 本地 git 登录的是个人账号 miaoQ777,仓库在工作账号 miao-Q777 解决:git push https://miao-Q777:TOKEN@github.com/miao-Q777/cloud-mail.git main单次指定认证
坑 5: 容器端口随机化
现象: Dokploy 重启后所有 Hostinger VPS 服务端口变化 根因: Dokploy compose 使用随机端口 解决: 建议改用 Traefik localhost:80 hostname 路由
⚠️ 已知限制 / 副作用
- Web UI 入口是 mail.gottenergy.com(邮箱地址可保持 @gottenergy.com)
- 收件未正式打通(等 Google Workspace 权限)
- 发件功能未配(需 Resend 或 CF 发件)
- Mailgun Webhook 缺签名验证(上线前必须加)
- 域名变更方法已验证:CNAME + Worker Route 改指向即可,D1 数据不受影响
🔒 安全 & 成本速查
- API Token: D1/KV/R2/Workers Edit + DNS Read
- JWT_SECRET: GitHub Secret + Workers 环境变量
- 月度成本: $0(CF 免费额度 + Mailgun 免费 100封/天)
- 超管: admin@gottenergy.com(D1 可改 role)
📌 待办 / 未来优化方向
- 拿到 Google Workspace 管理权限,配置 Split Delivery 路由规则
- Gmail 发测试邮件到未知用户@gottenergy.com,验证 Google → CF 转发链路
- 配发件功能(Resend 或 CF 自有发件)
- Mailgun Webhook 加签名验证(备选方案保留)
- Workers AI 验证码识别
- Telegram Bot 通知
📋 速查命令合集
# CF API 查 MX
curl -s -H "Authorization: Bearer TOKEN" \
"https://api.cloudflare.com/client/v4/zones/6fd73d8b4cbeb555b6c69afac5c31263/dns_records?type=MX" | python3 -m json.tool
# curl 测试 Mailgun Webhook
curl -X POST https://mail.gottenergy.com/api/webhooks/mailgun \
-F sender=test@gmail.com \
-F recipient=admin@mail.gottenergy.com \
-F subject="test" \
-F body-plain="Hello"
# Worker 日志
# CF Dashboard → Workers & Pages → cloud-mail → Observability📝 更新日志
| 日期 | 改动 | 原因 |
|---|---|---|
| 2026-07-10 | 首次创建,Fork+部署+绑定+初始化+注册完成 | 老师要求部署 Cloud Mail |
| 2026-07-10 | MX 冲突发现,Email Routing 无法激活 | Google Workspace + Mailgun 占用 |
| 2026-07-11 | 域名切到 mail.gottenergy.com,验证 CNAME+Worker Route 绑定 | 根域冲突 |
| 2026-07-11 | 开发 Mailgun Webhook 适配器,curl 验证通过 | CF Email Routing 不可用时的备选 |
| 2026-07-11 | 路由从 /mailgun-webhook 改为 /webhooks/mailgun | 绕过认证中间件 |
| 2026-07-11 | 决定采用 Split Delivery 方案(Google 做 MX,转发 CF) | 老师决策,@gottenergy.com 统一后缀 |