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

NameSecret
CLOUDFLARE_API_TOKENCF API Token
CLOUDFLARE_ACCOUNT_ID6d048f06c7a9c6a1737ebc019d8ad549
D1_DATABASE_IDcloud-mail-db UUID
KV_NAMESPACE_IDcloud-mail-kv UUID
R2_BUCKET_NAMEcloud-mail-r2
DOMAIN[“gottenergy.com”, “mail.gottenergy.com”]
ADMINadmin@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,两个文件:

  1. 新建 mail-worker/src/api/mailgun-api.js:Hono 路由,POST /webhooks/mailgun,解析 Mailgun form-data → 调 emailService.receive() → D1
  2. 修改 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

🔍 验证方法

  1. DB 初始化: curl https://mail.gottenergy.com/api/init/JWT_SECRET → success
  2. Web UI: 打开 https://mail.gottenergy.com → 登录页
  3. 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 适配打下了基础

🕳️ 踩过的坑


⚠️ 已知限制 / 副作用

  • 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-10MX 冲突发现,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 统一后缀