基于 Cloudflare Workers + D1 的临时邮箱服务(不含附件),支持匿名创建邮箱、接收邮件、认领获取 Key、通过 username + key 恢复访问(默认 15 天有效期,可续期),并提供管理后台用于域名/规则/隔离/审计与数据看板。
快速入口
- 文档:
spec/01spec.md(需求) /spec/02design.md(设计) /spec/03task.md(任务) - 本地开发:见「本地开发」
- 部署上线:见「从零开始部署(生产)」
- API:见「API 概览」
- 一键部署(主应用)
- 功能
- 架构概览
- 技术栈
- 本地开发
- 从零开始部署(生产)
- API 概览
- 安全与注意事项
- 常用命令
- 目录结构(简化)
- License
说明:
- 将链接中的
https://github.com/<OWNER>/<REPO>替换为你 fork 后的仓库地址 - 一键部署通常只会部署
wrangler.toml对应的主应用;Email Worker 与 Scheduled Worker 仍需按下方流程手动部署与配置
- 邮箱创建:随机生成或手动指定用户名(
random/manual) - 入站收信:Email Worker 解析并存储邮件(正文可截断,HTML 会净化;附件不存储)
- 认领系统:未认领邮箱可通过 Turnstile 验证后认领,返回一次性明文 Key
- 恢复访问:
username + domain + key恢复并创建会话(错误信息不区分邮箱不存在与 key 错误) - 管理后台:域名管理、规则(drop/quarantine/allow)、隔离队列、审计与仪表盘
flowchart LR
U[User Browser] -->|HTTPS| W[Main Worker: Next.js - OpenNext]
MTA[Cloudflare Email Routing] -->|Inbound Email| EW[Email Worker]
SW[Scheduled Worker] -->|Cron| D1[(D1 Database)]
W --> D1
EW --> D1
- Next.js App Router(OpenNext 适配 Cloudflare Workers)
- Cloudflare Workers / Email Workers / Scheduled Workers
- Cloudflare D1(SQLite)
- 用户端:MDUI 2(MD3 风格)+ Iconify(mdi)
- 管理端:TailAdmin + shadcn/ui(Tailwind 体系)+ Iconify(lucide)
- 包管理器:bun(禁止使用 npm / yarn / pnpm)
bun(包管理与脚本执行)wrangler(本项目已在devDependencies中提供,可用bunx wrangler)
bun install# 全部测试
bun test
# 单元测试 / 集成测试
bun run test:unit
bun run test:integration本项目运行在 Cloudflare Workers 环境中,通过 wrangler.toml / wrangler.email.toml / wrangler.scheduled.toml 配置 D1 绑定与环境变量。
本仓库包含 3 份 Wrangler 配置文件,分别对应 3 个独立 Worker(主应用、入站邮件、定时任务):
| 文件 | Worker 名称(name) |
入口(main) |
触发方式 | 用途 |
|---|---|---|---|---|
wrangler.toml |
flashinbox |
.open-next/worker.js |
HTTP 路由(routes) |
主应用(Next.js App Router + API) |
wrangler.email.toml |
flashinbox-email |
src/workers/email/index.ts |
Email Routing(Dashboard 配置) | 入站邮件解析与入库 |
wrangler.scheduled.toml |
flashinbox-scheduled |
src/workers/scheduled/cleanup.ts |
Cron([triggers].crons) |
清理/统计等定时任务 |
常用部署命令:
- 主应用:
wrangler deploy --env production(默认读取wrangler.toml) - Email Worker:
wrangler deploy --config wrangler.email.toml - Scheduled Worker:
wrangler deploy --config wrangler.scheduled.toml
关键字段含义(同名字段在 3 个文件中含义一致):
compatibility_date:Workers 兼容性日期,固定运行时行为,避免平台升级导致差异compatibility_flags = ["nodejs_compat"]:开启 Node.js 兼容层(用于依赖/部分 Node API 适配)main:Worker 入口文件路径assets = { directory, binding }:仅主应用使用的静态资源发布与绑定(OpenNext 产物)routes:仅主应用使用的 HTTP 路由规则(生产环境通常配置自定义域名)[env.dev]/[env.production]:仅主应用使用的多环境配置覆盖(用wrangler deploy --env <name>选择)[[d1_databases]]:D1 绑定(代码中通过env.DB访问)database_name:D1 实例名称database_id:D1 实例 ID(远程部署时必填;本地可为空或占位)
[vars]:非敏感环境变量(可在面板查看),用于业务参数(域名、过期时间、正文上限等)- Secrets:敏感变量(通过
wrangler secret put写入,不应出现在配置文件中) [triggers].crons:仅 Scheduled Worker 使用的 Cron 触发器列表(每个表达式都会触发一次执行)
主应用 Secrets(必需)
| Key | 用途 |
|---|---|
ADMIN_TOKEN |
管理后台登录令牌 |
KEY_PEPPER |
Key 哈希 pepper(SHA-256(key + pepper)) |
SESSION_SECRET |
会话签名密钥 |
TURNSTILE_SECRET_KEY |
Turnstile 服务端密钥 |
TURNSTILE_SITE_KEY |
Turnstile 前端 site key |
常用 Vars(见 wrangler.toml)
| Key | 说明 |
|---|---|
DEFAULT_DOMAIN |
默认邮箱域名 |
KEY_EXPIRE_DAYS |
Key 有效期(天) |
UNCLAIMED_EXPIRE_DAYS |
未认领邮箱过期(天) |
SESSION_EXPIRE_HOURS |
用户会话有效期(小时) |
ADMIN_SESSION_EXPIRE_HOURS |
管理会话有效期(小时) |
MAX_BODY_TEXT / MAX_BODY_HTML |
正文截断上限 |
RATE_LIMIT_* |
限流规则(如 10/10m) |
说明:配置解析与校验逻辑在 src/lib/types/env.ts。
Umami 统计使用 环境变量(Vars) 配置,不使用数据库配置。
仅主应用(wrangler.toml 对应 Worker)需要配置 Umami;Email / Scheduled Worker 无需配置。
需要设置以下变量(Vars):
| Key | 说明 |
|---|---|
UMAMI_SCRIPT_URL |
Umami 脚本地址,例如 https://analytics.example.com/script.js |
UMAMI_WEBSITE_ID |
用户端网站 ID(UUID) |
UMAMI_ADMIN_WEBSITE_ID |
管理端网站 ID(UUID,可选) |
设置方式(二选一):
-
写入
wrangler.toml的[vars](建议按环境分别配置到env.dev.vars/env.production.vars) -
Cloudflare Dashboard → Workers → Settings → Variables(为对应 Worker 配置 Vars)
示例(wrangler.toml / env.production.vars):
[env.production.vars]
UMAMI_SCRIPT_URL = "https://analytics.example.com/script.js"
UMAMI_WEBSITE_ID = "00000000-0000-0000-0000-000000000000"
UMAMI_ADMIN_WEBSITE_ID = "00000000-0000-0000-0000-000000000000" # optional本项目 CSP 由 src/middleware.ts 动态生成:
- 会自动从
UMAMI_SCRIPT_URL提取 origin,并加入script-src/connect-src - 若需要额外放行多个 Umami 源(例如多域名、代理层、或脚本/上报不在同一 origin),用环境变量追加 allowlist
| Key | 说明 |
|---|---|
UMAMI_ALLOWED_ORIGINS |
额外允许的 Umami 源(origin 列表,逗号或空格分隔),例如 https://analytics.example.com https://analytics2.example.com |
NEXT_PUBLIC_UMAMI_ALLOWED_ORIGINS |
同上(兼容本地 Next.js 环境变量命名) |
示例:
[env.production.vars]
UMAMI_ALLOWED_ORIGINS = "https://analytics.example.com https://analytics2.example.com"- 用户端通过
/api/user/config下发 Umami 配置,存在时自动注入脚本并在路由切换时调用umami.track()(见src/components/ui/UmamiLoader.tsx) - 管理端通过
/api/admin/config下发 Umami 配置(无需登录),同样自动注入脚本并跟踪路由切换 - 若浏览器控制台出现 CSP 报错,请确认
UMAMI_SCRIPT_URL与UMAMI_ALLOWED_ORIGINS覆盖了 Umami 脚本与上报请求的 origin,然后重新部署/刷新生效 - 管理端可在
/admin/settings查看当前 Umami 配置(页面为只读;修改环境变量后需重新部署/刷新生效)
# 创建本地 D1(名称可自行调整)
wrangler d1 create flashinbox-db-dev
# 执行迁移(本地)
wrangler d1 execute flashinbox-db-dev --local --file=migrations/0001_init.sql# Next.js 开发服务器(页面与 API)
bun run dev
# 构建 OpenNext Worker 并用 wrangler 启动(更接近生产)
bun run dev:wrangler
# 两者同时启动
bun run dev:allcurl -s http://127.0.0.1:8787/api/user/config目标:一次性部署 3 个 Worker(主应用 / Email / Scheduled),绑定同一个 D1,并在 Cloudflare Email Routing 中将入站邮件转发到 Email Worker。
- Cloudflare 账号(开通 Workers、D1、Email Routing、Turnstile)
- 一个用于收信的域名(例如
flashinbox.de),已接入 Cloudflare DNS,Email Routing 显示 Domain 为 Enabled - 本地已安装
bun与wrangler(本项目可用bunx wrangler) - 已 fork 本仓库并克隆到本地
bun installwrangler loginwrangler d1 create flashinbox-db将创建结果里的 database_id 填入以下配置(3 个 Worker 必须指向同一个 D1):
wrangler.toml:env.production.d1_databases[0].database_idwrangler.email.toml:d1_databases[0].database_idwrangler.scheduled.toml:d1_databases[0].database_id
wrangler d1 execute flashinbox-db --remote --file=migrations/0001_init.sql
wrangler d1 execute flashinbox-db --remote --file=migrations/0002_mailboxes_banned.sql说明:
- 若你看到类似 “please use ... instead of the SQL BEGIN TRANSACTION or SAVEPOINT” 的报错,请确保迁移文件中没有
BEGIN TRANSACTION/COMMIT/SAVEPOINT(本仓库的migrations/0002_mailboxes_banned.sql已按该要求编写)
说明:
0002仅用于启用邮箱禁用(banned)状态;如果不执行该迁移,后台“禁用邮箱”会因CHECK约束失败而报错
修改 wrangler.toml 的生产环境配置:
env.production.routes[0].pattern例如mail.flashinbox.deDEFAULT_DOMAIN设置为你的收信域名(例如flashinbox.de)
说明:
- Web 访问域名(例如
mail.flashinbox.de)与收信域名(例如flashinbox.de)可以不同;Email Routing 的 Catch-all 只对主域生效,不适用于mail.<domain>这种子域
Cloudflare Dashboard → Turnstile:
- 绑定主应用域名(例如
mail.flashinbox.de) - 获取
TURNSTILE_SITE_KEY与TURNSTILE_SECRET_KEY
wrangler secret put ADMIN_TOKEN --env production
wrangler secret put KEY_PEPPER --env production
wrangler secret put SESSION_SECRET --env production
wrangler secret put TURNSTILE_SECRET_KEY --env production
wrangler secret put TURNSTILE_SITE_KEY --env production说明:
KEY_PEPPER为关键安全配置,主应用用于 claim/recover 等需要写入mailboxes.key_hash的流程- Email Worker 与 Scheduled Worker 当前不需要 Secrets(仅需 D1 绑定与必要 Vars)
bun run build:worker
wrangler deploy --env productionwrangler deploy --config wrangler.email.tomlwrangler deploy --config wrangler.scheduled.tomlEmail Worker 会查询 D1 的 domains 表决定是否接收邮件;如果 domains 中没有你的域名,或状态为 disabled/readonly,入站邮件会被拒绝/丢弃(Cloudflare Email Routing 的 Activity log 通常显示 Result 为 Dropped)。
两种方式二选一:
- 使用管理后台添加域名:
- 访问
https://<your-app-domain>/admin - 输入
ADMIN_TOKEN登录 - 在 Domains 页面添加
flashinbox.de,状态选择enabled
- 直接写入 D1(远程):
wrangler d1 execute flashinbox-db --remote --command "INSERT INTO domains (name, status, note, created_at, updated_at) VALUES ('flashinbox.de', 'enabled', 'prod', strftime('%s','now')*1000, strftime('%s','now')*1000);"说明:
- 如果你有多个收信域名(例如
flashinbox.de+514819.xyz),每个域名都需要写入domains表并设置为enabled
在 Cloudflare Dashboard 中对你的域名开启 Email Routing,并创建路由规则:
- 推荐:启用 Catch-all address,Action 选择
Send to a Worker,Worker 选择flashinbox-email - 若 Catch-all 为
Drop或未配置为 Worker,发送到未匹配 Custom address 的邮件会被直接丢弃,通常不会产生退信 - Catch-all 规则只对主域生效,不能对每个子域单独创建(例如
mail.flashinbox.de这种子域不适用)
- 访问主应用域名(例如
https://mail.yourdomain.com) - 创建邮箱后,从外部邮箱向
username@yourdomain.com发送邮件,确认收件箱可见- 若 Email Routing 的 Activity log 显示 Result 为 Dropped:
- 确认
domains表中存在收信域名且为enabled wrangler tail flashinbox-email查看 Email Worker 日志,检查是否出现Domain not found/Domain is disabled/ 解析异常- 检查是否配置了规则将邮件
drop(Rules 页面)
- 确认
- 若 Email Routing 的 Activity log 显示 Result 为 Dropped:
用户侧:
POST /api/user/create:创建邮箱(mode=random|manual,可传username/domainId)POST /api/user/claim:认领邮箱(mailboxId或email+turnstileToken,返回一次性key)POST /api/user/recover:恢复访问(username+domain+key)POST /api/user/renew:续期(需用户会话)GET /api/user/domains:可用域名列表GET /api/user/config:前端配置(默认域名、Turnstile site key)
邮箱侧(需用户会话):
GET /api/mailbox/info:邮箱信息与未读数GET /api/mailbox/inbox:收件箱列表(分页、未读过滤、搜索)
管理侧(需管理员会话):
POST /api/admin/login:管理员登录(token+fingerprint)POST /api/admin/logout:退出GET/POST /api/admin/domains:域名管理GET/POST /api/admin/rules:规则管理GET /api/admin/mailboxes:邮箱列表(支持过滤)GET /api/admin/mailboxes/:mailboxId:邮箱详情PATCH /api/admin/mailboxes/:mailboxId:禁用邮箱({ "status": "banned" })DELETE /api/admin/mailboxes/:mailboxId:删除邮箱(destroy,删除关联消息/隔离/会话并标记销毁)GET /api/admin/quarantine:隔离队列GET /api/admin/dashboard:仪表盘数据(range=24h|7d|30d)
- Key 不以明文存储:使用
SHA-256(key + pepper),比较使用恒定时间比较 - 恢复接口错误不区分“邮箱不存在”和“Key 错误”,降低信息泄露风险
- HTML 邮件会净化后再渲染(避免 XSS)
- 中间件统一设置安全响应头与 CSP:用户站点允许 Turnstile,管理后台更严格(见
src/middleware.ts)
| 目的 | 命令 |
|---|---|
| 本地开发 | bun run dev |
| 本地 Workers | bun run dev:wrangler |
| 同时启动 | bun run dev:all |
| 构建 Worker | bun run build:worker |
| 部署主应用 | wrangler deploy --env production |
| 格式化 | bun run format |
| Lint | bun run lint |
| 类型检查 | bun run typecheck |
| 测试 | bun test |
src/app/ Next.js App Router(页面与 API)
src/lib/ 核心库(db/services/utils/middleware/types)
src/workers/ Email Worker 与 Scheduled Worker
migrations/ D1 迁移
spec/ 需求与设计文档
MIT(见 LICENSE)