一个两段式邮件收集项目:
- Cloudflare Worker 负责接收邮件并转发原始内容
- FastAPI 负责鉴权、解析 raw 邮件并写入 PostgreSQL
- email-workers
.
├─ _worker.js # Cloudflare Email Worker
├─ wrangler.toml # Wrangler 部署配置
├─ main.py # 本地启动入口
├─ app/
│ ├─ __init__.py # FastAPI 应用入口
│ ├─ config.py # 环境变量与常量
│ ├─ database.py # 数据库连接与建表
│ ├─ mail_parser.py # 邮件解析与附件提取
│ ├─ models.py # Pydantic 模型
│ ├─ sql.py # SQL 常量
│ ├─ utils.py # 通用工具
│ ├─ routes/ # 路由层
│ ├─ services/ # 服务层
│ └─ templates/ # HTML / JS / CSS 模板文件
├─ Dockerfile # 应用镜像构建文件
├─ docker-compose.yml # 本地/单机部署编排文件
└─ requirements.txt # Python 依赖
Cloudflare Email Routing
│
▼
Cloudflare Worker (_worker.js)
│ POST /internal/emails
▼
FastAPI (app package)
│
▼
PostgreSQL
Worker 现在只做这几件事:
- 接收 Cloudflare Email 事件
- 读取原始邮件文本
rawText - 携带基础信封信息
mailFrom、rcptTo、receivedAt - 使用
API_TOKEN调用 FastAPI 的/internal/emails
不提供公开 HTTP 接口。
FastAPI 负责:
- 校验所有 API 路由的
API_TOKEN - 解析 raw 邮件内容
- 提取
Message-ID、Subject、Date、头信息、发件地址 - 将邮件写入 PostgreSQL
- 提供控制台页面、列表查询、详情查询、历史清理接口
数据库表会在启动时自动初始化。
必须配置:
DATABASE_URL:PostgreSQL 连接串API_TOKEN:统一鉴权 TokenPORT:可选,默认8000
示例:
DATABASE_URL=postgresql://user:[email protected]:5432/maildb
API_TOKEN=your-secret-token
PORT=8000必须配置:
BACKEND_BASE_URL:FastAPI 对外可访问的公开 HTTPS 域名根地址,例如https://api.example.comAPI_TOKEN:与 FastAPI 保持一致
BACKEND_BASE_URL 必须注意:
- 这里只能写公开可访问的域名根地址
- 不要写 IP 地址,例如
https://1.2.3.4 - 不要写本地地址,例如
http://localhost:8000 - 不要写 Docker 内部服务名,例如
http://email-workers-python:8000 - 不要把路径写进去,例如
https://api.example.com/internal/emails
Worker 会自动拼接 /internal/emails。
fastapi>=0.122.0
uvicorn>=0.30.0
psycopg[binary]>=3.2.10安装依赖:
pip install -r requirements.txt项目已提供 environment.yml,可用于本地快速创建运行环境。
conda env create -f environment.ymlconda activate email-workerspython main.py如果你修改了依赖,可重新更新环境:
conda env update -f environment.yml --prunepython main.py或者:
uvicorn app:app --host 0.0.0.0 --port 8000如果使用 Docker,请直接看下方“Docker 部署”章节。
启动后默认地址:
- 控制台首页:
/ - 文档页:
/docs - Swagger:
/openapi - 健康检查:
/healthz
除健康检查和页面访问外,所有 API 路由都使用:
Authorization: Bearer API_TOKEN包括内部写入接口:
POST /internal/emails
Worker 发给 FastAPI 的请求体如下:
{
"mailFrom": "[email protected]",
"rcptTo": "[email protected]",
"receivedAt": "2026-04-03T12:00:00.000Z",
"rawText": "Raw RFC822 message text"
}其中:
mailFrom:信封发件人rcptTo:信封收件人receivedAt:Worker 接收时间rawText:邮件原文
GET /api/auth/verifyGET /api/mails?rcptTo=&after=&before=&page=1&pageSize=20参数:
rcptTo:按收件邮箱筛选after:开始时间,ISO 格式before:结束时间,ISO 格式page:页码,从 1 开始pageSize:每页条数,最大 100
GET /api/mails/{mail_id}GET /api/mail/{email}?after=&before=&page=1&pageSize=20GET /api/mail/{email}/{mail_id}POST /api/admin/cleanup-history
Content-Type: application/json请求体:
{
"before": "2026-04-01T00:00:00.000Z"
}不传 before 时,默认清理一天前的数据。
当前表会保存:
idmessage_idmail_fromrcpt_tosubjectdate_headerreceived_atheaders_jsonraw_textcreated_at
并对 (message_id, rcpt_to) 做唯一约束,避免重复入库。
部署到任意可访问 PostgreSQL 的 Python 运行环境即可。
要求:
- 能访问 PostgreSQL
- 能被 Cloudflare Worker 通过公网访问到
/internal/emails - 设置好
DATABASE_URL和API_TOKEN
部署 _worker.js 后:
- 配置 Cloudflare Email Routing 到该 Worker
- 配置 Worker 环境变量:
BACKEND_BASE_URLAPI_TOKEN
- 确保 FastAPI 的
/internal/emails能通过公网 HTTPS 域名访问 - 如果 Worker 返回
403 error code: 1003,优先检查BACKEND_BASE_URL是否写成了 IP、本地地址、Docker 服务名或错误的路径
项目提供了环境变量示例文件:.env.example。
可先复制为本地 .env:
cp .env.example .env如果你在 Windows PowerShell 中执行,可使用:
Copy-Item .env.example .env然后按需修改其中的关键配置:
DATABASE_URL:本地直连 PostgreSQL 时使用API_TOKEN:FastAPI 与 Worker 共用鉴权 TokenPORT:本地运行端口ATTACHMENTS_DIR:附件落盘目录BACKEND_BASE_URL:Cloudflare Worker 对外访问后端的 HTTPS 域名根地址
docker-compose.yml 默认会读取宿主机环境变量中的 API_TOKEN;如果你希望直接从 .env 加载,可先执行导出,或按你的运行方式让 Compose 读取 .env。
当前 docker-compose.yml 默认直接使用预构建镜像:
docker compose up -d如果你想先手动本地打包再启动,可使用:
docker build -t ghcr.io/likesrt/email-workers-python:latest .
docker compose up -d或者把 docker-compose.yml 中注释掉的 build: 配置恢复后执行:
docker compose up -d --build启动后:
- FastAPI 控制台:
http://127.0.0.1:8000/ - 文档页:
http://127.0.0.1:8000/docs - Swagger:
http://127.0.0.1:8000/openapi - PostgreSQL:
127.0.0.1:5432
app:FastAPI 服务,容器内监听8000db:PostgreSQL 16,默认库名maildbpostgres_data:持久化数据库数据mail_attachments:持久化邮件附件文件
docker-compose.yml 中应用默认使用:
DATABASE_URL=postgresql://mail:mail@db:5432/maildb
对应 PostgreSQL 默认账号:
POSTGRES_DB=maildbPOSTGRES_USER=mailPOSTGRES_PASSWORD=mail
如需修改,可直接调整 docker-compose.yml。
docker compose down如果你还想连同数据卷一起删除:
docker compose down -v当 FastAPI 通过 Docker 部署后,Worker 的 BACKEND_BASE_URL 仍然必须填写对外可访问的 HTTPS 域名根地址。
例如:
https://mail.example.com
不要填写:
http://localhost:8000http://db:5432http://email-workers-app:8000https://your-domain/internal/emails
README 顶部已经提供官方 Deploy to Cloudflare 按钮,点击后会跳转到 Cloudflare 的一键部署页面。
适合场景:
- 想快速把当前仓库复制到自己的 Cloudflare / GitHub 流程中
- 不想先本地安装 Wrangler
- 需要先在 Cloudflare 控制台完成初始化部署
按钮使用的是 Cloudflare 官方格式:
[](https://deploy.workers.cloudflare.com/?url=https://github.com/likesrt/email-workers-python)如果你想手动部署 _worker.js,可以使用以下两种方式。
- 登录 Cloudflare 控制台
- 打开
Workers & Pages - 创建或导入 Worker
- 将 _worker.js 内容粘贴到 Worker 编辑器
- 在 Worker 设置中补充环境变量
- 保存并部署
项目已提供 wrangler.toml,可直接通过 Wrangler 部署:
npx wrangler deploy部署前需先配置环境变量(以 secret 方式写入,不要明文写在 wrangler.toml 中):
npx wrangler secret put BACKEND_BASE_URL
npx wrangler secret put API_TOKEN部署 Worker 时,至少需要配置:
BACKEND_BASE_URLAPI_TOKEN
建议值说明:
BACKEND_BASE_URL:FastAPI 的公网 HTTPS 域名根地址API_TOKEN:必须与 FastAPI 后端完全一致
正确示例:
BACKEND_BASE_URL=https://mail.example.com
API_TOKEN=your-secret-token
错误示例:
BACKEND_BASE_URL=http://localhost:8000
BACKEND_BASE_URL=http://email-workers-python:8000
BACKEND_BASE_URL=https://mail.example.com/internal/emails
Worker 部署完成后,建议检查:
- Worker 是否已成功发布
BACKEND_BASE_URL是否是公网 HTTPS 根地址API_TOKEN是否与 FastAPI 完全一致- FastAPI 的
/internal/emails是否可被公网访问 - 发送测试邮件后,FastAPI 是否成功入库
官方前提是:你的域名已经托管在 Cloudflare,并启用了 Email Routing。
基本步骤:
- 进入 Cloudflare 控制台
- 选择你的域名
- 打开
Email或Email Routing - 按向导启用 Email Routing
- 根据 Cloudflare 提示完成所需 DNS 记录配置
如果控制台提示需要添加或修改 DNS 记录,应先完成这些记录后再继续。
Cloudflare 官方说明里,Email Routing 通常要求先添加并验证目标邮箱。
步骤:
- 在 Email Routing 中添加目标邮箱
- Cloudflare 会发送验证邮件到该邮箱
- 打开验证邮件并完成确认
- 验证成功后,该邮箱即可作为转发目标或相关配置前提
你的目标不是直接转发到普通邮箱,而是让邮件进入 Worker。
推荐流程:
- 在 Email Routing 中添加新的路由规则
- 匹配收件地址,例如:
catch-all[email protected]*@example.com
- 动作选择投递到 Worker
- 选择你部署好的 Worker
这样邮件到达 Cloudflare 后,会直接触发 Worker 的 email(message, env, ctx) 处理逻辑。
Cloudflare Worker 需要具备 Email handler,也就是当前 _worker.js 这类接收邮件事件的逻辑。
Cloudflare 官方 Email handler 入口形式类似:
export default {
async email(message, env, ctx) {
// handle message
},
}你的 Worker 已经属于这一类用途:接收邮件事件、读取 raw 内容、再转发给 FastAPI。
因此要确认两点:
- Email Routing 规则已经把邮件投递到这个 Worker
- Worker 已经正确配置
BACKEND_BASE_URL和API_TOKEN
完成路由后,建议做一次完整测试:
- 从外部邮箱向你的域名地址发邮件
- 确认 Cloudflare Worker 是否被触发
- 确认 Worker 是否成功请求 FastAPI
/internal/emails - 在 FastAPI 控制台中检查邮件是否出现
- 如有附件,确认附件是否也被正确保存
优先检查:
- 域名是否真的启用了 Email Routing
- MX / 相关 DNS 记录是否已按 Cloudflare 要求生效
- 路由规则是否命中目标收件地址
- 路由动作是否真的绑定到了目标 Worker
优先检查:
BACKEND_BASE_URL是否填写正确API_TOKEN是否一致- FastAPI
/internal/emails是否公网可达 - FastAPI 是否能连 PostgreSQL
- FastAPI 日志中是否存在 401 / 500 / 解析异常
这通常优先检查 BACKEND_BASE_URL:
- 不要填 IP
- 不要填
localhost - 不要填 Docker 内部服务名
- 不要把
/internal/emails路径写进去 - 必须是公网 HTTPS 域名根地址
项目已提供工作流文件:
触发规则:
push到mainpull_request- 手动触发
workflow_dispatch
行为说明:
- PR / 手动触发:仅构建 Docker 镜像,快速验证是否可打包
main分支推送:构建并推送镜像到ghcr.io/likesrt/email-workers-python
镜像标签:
latestsha-<7位提交号>
如果要使用 GHCR,请确保仓库允许 GitHub Actions 写入 packages。
如果邮件未入库,可优先检查:
- Worker 的
BACKEND_BASE_URL是否是公开 HTTPS 域名根地址 - Worker 和 FastAPI 的
API_TOKEN是否一致 - FastAPI 是否能连通 PostgreSQL
- Cloudflare Email Routing 是否已绑定到该 Worker
- FastAPI 服务日志中是否有解析或写入错误