基于 Cloudflare 的简约响应式邮箱服务,支持邮件发送、附件收发 🎉
简体中文 | English
只需要一个域名,就可以创建多个不同的邮箱,类似各大邮箱平台,本项目支持署到 Cloudflare Workers ,降低服务器成本,搭建自己的邮箱服务
![]() |
![]() |
|---|---|
![]() |
![]() |
-
💰 低成本使用: 可部署到 Cloudflare Workers 降低服务器成本
-
💻 响应式设计:响应式布局自动适配PC和大部分手机端浏览器
-
📧 邮件发送:集成Resend发送邮件,支持群发,内嵌图片和附件发送,发送状态查看
-
🛡️ 管理员功能:可以对用户,邮件进行管理,RABC权限控制对功能及使用资源限制
-
📦 附件收发:支持收发附件,使用R2对象存储保存和下载文件
-
🔔 邮件推送:接收邮件后可以转发到TG机器人或其他服务商邮箱
-
📡 开放API:支持使用API批量生成用户,多条件查询邮件
-
📈 数据可视化:使用ECharts对系统数据详情,用户邮件增长可视化显示
-
🎨 个性化设置:可以自定义网站标题,登录背景,透明度
-
🤖 人机验证:集成Turnstile人机验证,防止人机批量注册
-
📜 更多功能:正在开发中...
-
Web框架:Hono
-
ORM:Drizzle
-
前端框架:Vue3
-
UI框架:Element Plus
-
邮件推送: Resend
-
数据库:Cloudflare D1
-
文件存储:Cloudflare R2
cloud-mail
├── mail-worker # worker后端项目
│ ├── src
│ │ ├── api # api接口层
│ │ ├── const # 项目常量
│ │ ├── dao # 数据访问层
│ │ ├── email # 邮件处理接收
│ │ ├── entity # 数据库实体
│ │ ├── error # 自定义异常
│ │ ├── hono # web框架配置、拦截器、全局异常等
│ │ ├── i18n # 语言国际化
│ │ ├── init # 数据库缓存初始化
│ │ ├── model # 响应体数据封装
│ │ ├── security # 身份权限认证
│ │ ├── service # 业务服务层
│ │ ├── template # 消息模板
│ │ ├── utils # 工具类
│ │ └── index.js # 入口文件
│ ├── pageckge.json # 项目依赖
│ └── wrangler.toml # 项目配置
│
├── mail-vue # vue前端项目
│ ├── src
│ │ ├── axios # axios配置
│ │ ├── components # 自定义组件
│ │ ├── echarts # echarts组件导入
│ │ ├── i18n # 语言国际化
│ │ ├── init # 入站初始化
│ │ ├── layout # 主体布局组件
│ │ ├── perm # 权限认证
│ │ ├── request # api接口
│ │ ├── router # 路由配置
│ │ ├── store # 全局状态管理
│ │ ├── utils # 工具类
│ │ ├── views # 页面组件
│ │ ├── app.vue # 入口组件
│ │ ├── main.js # 入口js
│ │ └── style.css # 全局css
│ ├── package.json # 项目依赖
└── └── env.release # 项目配置
部署或更新 mail-worker 后,必须让生产 D1 执行一次项目内置升级流程。否则会出现“Worker 代码已经使用新表/新字段,但 D1 仍是旧结构”的问题。
本项目的升级入口是:
GET /api/init/:secret
其中 :secret 是 Worker 环境变量里的 jwt_secret,不要写进 Git、README、日志或公开聊天。
mail-worker/src/init/init.js 中的 dbInit.init() 会按顺序执行:
intDB
v1_1DB
v1_2DB
...
v2_9DB
v3SmtpDB
v3SmtpLifecycleDB
settingService.refresh
这些步骤会补齐 CloudMail 基础表、SMTP 凭据表、SMTP submission 表、Resend webhook inbox、失败通知 outbox,以及老表上的新增字段。
例如 SMTP lifecycle 升级会给老 email 表增加:
smtp_submission_id
并创建相关索引。如果生产 D1 没有这个字段,而新版 Worker 已经部署,就可能出现:
/api/email/send 返回 500
但 Resend 实际已经发送成功
原因是发信流程中 Resend 会先发送,之后 Worker 写入 D1 email 表时发现缺列并报错。
每次生产部署后都执行一次:
https://<你的 CloudMail 域名>/api/init/<jwt_secret>
返回:
success
才算升级完成。
GitHub Actions 部署时也应保留部署后的初始化步骤:
curl -sL "https://<你的 CloudMail 域名>/api/init/${JWT_SECRET}"该升级流程可以重复执行:
CREATE TABLE IF NOT EXISTS会跳过已存在的表;CREATE INDEX IF NOT EXISTS会跳过已存在的索引;- 新增列通过
addColumns()包装,遇到duplicate column会忽略,其他错误才会中断。
因此,正常更新后重复调用一次 init 是推荐操作。
可以只读检查 D1 schema,确认关键列存在:
wrangler d1 execute <数据库名> --remote --command \
"SELECT COUNT(*) AS has_column FROM pragma_table_info('email') WHERE name = 'smtp_submission_id';"预期:
has_column = 1
还应确认 SMTP 相关表存在,例如:
smtp_credential
smtp_submission
smtp_recipient
smtp_notification
smtp_quota_reservation
smtp_provider_attempt
resend_webhook_inbox
smtp_provider_failure_outbox
如果忘记执行升级,常见表现包括:
D1_ERROR: table email has no column named smtp_submission_id
/api/email/send 返回 500,但 Resend 已发送成功
SMTP submission 入库失败
Resend webhook 或失败通知无法关联邮件记录
遇到这种情况,不要重复点击发送,先执行数据库升级并验证 schema。
本项目采用 MIT 许可证




