最近我用自己的域名 swrited.top 搭了一个轻量级邮箱网站:
1https://email.swrited.top它可以实现:
- 创建
@swrited.top邮箱 - 接收验证码邮件
- 网页查看收件箱
- 自动提取验证码
- 网页发送邮件
- 任意地址 Catch-all 收信
- 不需要服务器开放公网 25 端口
最终方案是:
1Cloudflare Email Routing 收信2Cloudflare Email Worker 转发邮件到后端3SendGrid SMTP Relay 负责发信4Flask + HTML 实现 WebMail 页面5Caddy 负责 HTTPS 反向代理这篇文章记录一下完整搭建过程。
说明:文章中所有 API Key、Token、服务器密码等敏感信息均已隐藏或替换。
一、为什么要这样搭?
最开始的想法很简单:
1用自己的服务器监听 25 端口,直接接收 @swrited.top 邮件但实际部署时遇到了一个问题:
很多云服务器厂商会限制 25 端口。
我的情况是:
- 本机可以监听
25 - 本机投递邮件正常
- DNS/MX 配置也正常
- 但公网连接服务器
25端口超时
这意味着 Gmail、Hotmail 等外部邮件服务器无法把邮件直接投递到我的 ECS。
所以最后换了思路:
1不让自己的服务器直接收公网 25 邮件2而是让 Cloudflare 帮我收邮件3再通过 HTTPS 推送到我的后端这样服务器只需要开放 443,不需要开放 25。
二、最终架构
整体链路如下:
1发件人 Gmail / Hotmail / 其他邮箱2 │3 ▼4Cloudflare Email Routing5 │6 ▼7Cloudflare Email Worker8 │9 ├── 转发到个人 Hotmail10 │11 └── POST 到 https://email.swrited.top/inbound12 │13 ▼14 Flask 后端保存邮件15 │16 ▼17 WebMail 前端显示发信链路:
1WebMail 写邮件2 │3 ▼4Flask /send API5 │6 ▼7SendGrid SMTP Relay :5878 │9 ▼10目标邮箱这个方案的优点:
- 不需要公网开放
25 - 可以 Catch-all 收任意
@swrited.top地址 - 邮件能同时进网页收件箱和转发到 Hotmail
- 发信走 SendGrid,避开云服务器出站 25 限制
- 整体比较轻量,不需要搭完整 Postfix/Dovecot 邮件系统
三、DNS 配置
DNS 托管在 Cloudflare。
1. WebMail 站点解析
1Type: A2Name: email3Value: 服务器公网 IP4TTL: Auto访问地址:
1https://email.swrited.top2. Cloudflare Email Routing MX
启用 Cloudflare Email Routing 后,需要把域名 MX 改成 Cloudflare 提供的记录。
示例:
1MX @ route1.mx.cloudflare.net2MX @ route2.mx.cloudflare.net3MX @ route3.mx.cloudflare.net同时 Cloudflare 会要求添加 SPF/DKIM 相关 TXT 记录,例如:
1TXT @ v=spf1 include:_spf.mx.cloudflare.net ~all2TXT cf2024-1._domainkey v=DKIM1; ...这些记录建议直接用 Cloudflare 页面里的“添加”按钮自动添加,避免手动复制 DKIM 长文本出错。
3. SendGrid 域名认证
SendGrid 发信也需要做域名认证。
一般会给出几条 CNAME/TXT,例如:
1CNAME emxxxx.example.com uxxxx.wl.sendgrid.net2CNAME s1._domainkey.example.com s1.domainkey.uxxxx.wl.sendgrid.net3CNAME s2._domainkey.example.com s2.domainkey.uxxxx.wl.sendgrid.net4TXT _dmarc.example.com v=DMARC1; p=none;添加完成后在 SendGrid 后台点击 Verify。
建议:
1SendGrid 相关 CNAME 使用 DNS only四、Cloudflare Email Routing 配置
进入 Cloudflare:
1域名 → 电子邮件 → 电子邮件路由1. 添加目标地址
先在:
1目标地址添加自己的真实邮箱,比如:
1yourname@hotmail.comCloudflare 会发送确认邮件,必须点击确认后才能使用。
2. 开启 Catch-all
进入:
1路由规则找到:
1Catch-all 地址开启后,所有地址都会被接收:
1anything@example.com2abc123@example.com3hello@example.com最初可以先设置为:
1操作:发送到电子邮件2目标:yourname@hotmail.com这样可以先验证 Cloudflare 收信是否正常。
五、Cloudflare Email Worker
为了让邮件进入我们自己的网页收件箱,需要创建 Email Worker。
入口:
1Cloudflare → 电子邮件 → 电子邮件 Workers创建一个 Worker,例如:
1swrited-mail-worker示例代码:
1export default {2 async email(message, env, ctx) {3 const raw = await new Response(message.raw).text();4
5 await fetch("https://email.example.com/inbound", {6 method: "POST",7 headers: {8 "Content-Type": "application/json",9 "X-Inbound-Token": "替换成自己的安全 Token"10 },11 body: JSON.stringify({12 id: crypto.randomUUID(),13 from: message.from,14 to: [message.to],15 subject: message.headers.get("subject") || "",16 raw: raw,17 text: raw,18 html: ""19 })20 });21
22 // 可选:同时转发到自己的真实邮箱23 await message.forward("yourname@hotmail.com");24 }25}部署 Worker 后,回到:
1电子邮件路由 → 路由规则 → Catch-all把操作从:
1发送到电子邮件改成:
1发送到 Worker选择刚创建的 Worker。
这样所有 @example.com 邮件都会:
- 进入网页收件箱
- 同时转发到真实邮箱
六、后端服务设计
后端使用 Python + Flask。
目录结构:
1/opt/yyds-mail/2├── server.py3├── apikey.txt4├── inbound_token.txt5├── sendgrid_key.txt6└── web/7 └── index.html核心功能:
/accounts创建临时邮箱/messages获取邮件列表/messages/<id>查看邮件详情/send发送邮件/inbound接收 Cloudflare Worker 推送的邮件/health健康检查
七、/inbound 接口
Cloudflare Email Worker 会把邮件通过 HTTPS POST 到:
1https://email.example.com/inbound后端会验证请求头:
1X-Inbound-Token: 自定义安全 Token收到后做几件事:
- 验证 Token
- 读取
raw原始邮件 - 解析 RFC822 邮件内容
- 提取 Subject、From、To、text/plain、text/html
- 存入收件箱
- 前端刷新后显示
需要注意的是:
Cloudflare Worker 传来的通常是完整 raw 邮件,如果不解析,前端看到的可能是一大段 MIME 原文,甚至像空白。
所以后端需要解析:
1parsed = email.message_from_string(raw)然后遍历 multipart:
1for part in parsed.walk():2 if part.get_content_type() == 'text/plain':3 # 提取文本正文4 elif part.get_content_type() == 'text/html':5 # 提取 HTML 正文八、Web API 简介
创建邮箱
1POST /accounts2Content-Type: application/json请求:
1{2 "localPart": "test123",3 "domain": "example.com"4}返回:
1{2 "address": "test123@example.com",3 "tempToken": "临时访问令牌",4 "accountId": "邮箱 ID"5}获取邮件列表
1GET /messages?address=test123@example.com&limit=202Authorization: Bearer <tempToken>获取邮件详情
1GET /messages/<message_id>?address=test123@example.com2Authorization: Bearer <tempToken>发送邮件
1POST /send2Authorization: Bearer <tempToken>3Content-Type: application/json请求:
1{2 "from": "test123@example.com",3 "to": "someone@example.net",4 "subject": "测试邮件",5 "body": "这是一封测试邮件"6}为了防止滥用,可以加限流,例如:
1每个邮箱每小时最多发送 10 封九、SendGrid 发信
由于云服务器一般会限制出站 25,所以发信用 SendGrid SMTP Relay。
SendGrid SMTP 参数:
1Server: smtp.sendgrid.net2Port: 5873Username: apikey4Password: SendGrid API KeyPython 示例:
1import smtplib2from email.mime.text import MIMEText3
4msg = MIMEText("邮件正文", _charset="utf-8")5msg["Subject"] = "测试邮件"6msg["From"] = "test@example.com"7msg["To"] = "someone@example.net"8
9s = smtplib.SMTP("smtp.sendgrid.net", 587, timeout=30)10s.ehlo()11s.starttls()12s.ehlo()13s.login("apikey", SENDGRID_API_KEY)14s.sendmail("test@example.com", ["someone@example.net"], msg.as_string())15s.quit()十、前端页面
前端是一个单页 HTML。
主要功能:
- 创建邮箱
- 随机邮箱名前缀
- 复制邮箱地址
- 自动刷新收件箱
- 查看邮件内容
- 自动提取 4-8 位验证码
- 写邮件
- 移动端适配
页面结构大概是:
1Swrited Mail2
3[创建邮箱输入框]4
5Tabs:6- 收件箱7- 写邮件用户流程:
11. 打开 https://email.example.com22. 输入邮箱前缀,例如 test12333. 点击创建邮箱44. 使用 test123@example.com 注册网站55. 等待验证码邮件进入网页收件箱66. 点击邮件查看验证码十一、Caddy 反向代理
Caddy 配置示例:
1email.example.com {2 reverse_proxy 127.0.0.1:80003}如果 Flask 服务运行在 Docker 网络或宿主机网关上,可以根据实际情况调整地址。
重载 Caddy:
1sudo systemctl reload caddy如果 Caddy 在 Docker 容器内:
1sudo docker exec caddy caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile十二、systemd 服务
为了让后端开机自启,可以创建:
1/etc/systemd/system/yyds-mail.service内容:
1[Unit]2Description=YYDS Mail Server3After=network.target4
5[Service]6Type=simple7User=root8ExecStart=/usr/bin/python3 -u /opt/yyds-mail/server.py9Restart=always10RestartSec=511
12[Install]13WantedBy=multi-user.target启用:
1sudo systemctl daemon-reload2sudo systemctl enable --now yyds-mail查看状态:
1systemctl status yyds-mail --no-pager -l查看日志:
1journalctl -u yyds-mail -n 100 --no-pager十三、测试方法
1. 测试健康检查
1curl https://email.example.com/health2. 测试创建邮箱
1curl -s -X POST https://email.example.com/accounts \2 -H 'Content-Type: application/json' \3 -d '{"localPart":"webtest"}'3. 测试 Worker 推送
1curl -s -X POST https://email.example.com/inbound \2 -H 'Content-Type: application/json' \3 -H 'X-Inbound-Token: 替换成自己的 Token' \4 -d '{5 "to":["webtest@example.com"],6 "from":"tester@example.net",7 "subject":"worker inbound test",8 "text":"hello from worker api"9 }'4. 测试真实收信
从 Hotmail/Gmail 发一封到:
1webtest@example.com然后观察后端日志:
1journalctl -u yyds-mail -n 100 --no-pager如果看到类似:
1[INBOUND] Worker 推送邮件 → webtest@example.com | test subject说明 Cloudflare Email Worker 已经打通。
十四、遇到的问题
1. 服务器公网 25 不通
表现:
1公网检测 25 端口 Connection timed out解决:
1不用服务器直接收 25,改用 Cloudflare Email Routing + Email Worker2. 前端 401
表现:
1/messages?... 401 Unauthorized原因:
后端重启后,内存里的邮箱 token 丢失,浏览器还在用旧 token 刷新。
解决:
1刷新页面,重新创建同名邮箱更好的方案是后续加数据库持久化。
3. Worker 邮件进来了但内容看不了
原因:
Cloudflare Worker 推送的是 raw 邮件,前端直接显示会很乱。
解决:
后端解析 raw 邮件,提取 text/plain 和 text/html。
4. Catch-all 只转发到 Hotmail,不进网页
原因:
Catch-all 操作还是“发送到电子邮件”。
解决:
把 Catch-all 改成:
1发送到 Worker然后在 Worker 里:
1await message.forward("yourname@hotmail.com");这样既进网页,也转发到 Hotmail。
十五、当前限制
当前版本仍然比较轻量,有一些限制:
- 邮件存在内存里,后端重启会清空
- 页面刷新后 token 可能丢失
- 没有账号登录系统
- 没有附件管理
- HTML 邮件没有做完整安全沙箱
- 发信依赖 SendGrid 账号状态
- 防滥用能力比较基础
十六、后续优化方向
可以继续加:
- SQLite/PostgreSQL 持久化
- 邮箱有效期
- 管理后台
- 用户登录
- IP 限流
- Cloudflare Turnstile 人机验证
- 附件存储
- HTML 邮件沙箱渲染
- 邮件搜索
- SPF/DKIM/DMARC 检测
- SendGrid API Key 轮换
总结
这套方案的核心是:
1Cloudflare 收信,Worker 推送,SendGrid 发信相比自己搭完整邮件服务器,它更轻量,也更适合云服务器 25 端口受限的场景。
最终链路:
1别人发邮件 → Cloudflare Email Routing → Email Worker → WebMail 后端 → 网页收件箱发信链路:
1网页写邮件 → Flask API → SendGrid SMTP Relay → 对方邮箱对于个人项目、验证码接收、临时邮箱、自动化注册测试,这个方案已经比较实用了。
Some information may be outdated