域名邮箱转到 Gmail 被丢垃圾邮件?用 Gmail API 把邮件直接写进收件箱

从转发到 Gmail 说起
我的域名邮箱一直用 Cloudflare Email Routing 收信,最开始直接转发到 Gmail。配置很简单,用了一阵才发现很多正规平台的邮件都没收到:npm 的验证码、HSBC 的通知,甚至 Apple 的邮件。Cloudflare 的日志显示已经转发成功,最后在 Gmail 的垃圾邮件里找到了它们。
问题出在转发这个动作上。Cloudflare 收下邮件后,会用自己的服务器把它再投递一次给 Gmail。在 Gmail 看来,这封邮件自称来自 HSBC,却是 Cloudflare 的服务器送来的。Gmail 只能靠原始的 DKIM 签名和它对这条链路的信任来判断真假,判断不准的就进了垃圾邮件。
换成 mail2telegram 之后
后来我换成了 mail2telegram,这篇文章的旧版本写的就是它的部署教程。邮件交给 Cloudflare Worker 处理,直接推送到 Telegram Bot。为了长期保存,我还配了 FORWARD_LIST,让它把邮件顺便备份到 Gmail。
这个备份在 Worker 里调用的是 message.forward(),本质上还是 Cloudflare 把邮件再投递一遍,和最开始的转发走同一条路。Telegram 这边的问题解决了,备份那边的老问题还在。
让我决定换掉它的是 Steam 的邮件。大概是 Steam 发信那边的某项配置有问题,这些邮件在转发时直接被 Cloudflare 拦了下来,Gmail 里根本收不到。以前进了垃圾箱,至少还能翻出来,这回连备份都没有。
只要备份还靠「再发一次」,就得看中间每一环的脸色:从发件方的认证配置到 Cloudflare 的检查,再到 Gmail 的反垃圾策略,哪一环不认,邮件就到不了。所以我决定备份这一步干脆不发信。
换个思路:用 Gmail API 直接写入
Gmail API 有一个 users.messages.insert 接口,官方文档是这么描述的:
Directly inserts a message into only this user’s mailbox similar to IMAP APPEND, bypassing most scanning and classification. Does not send a message.
意思是它和 IMAP 的 APPEND 类似,把一封邮件直接放进这个用户自己的邮箱,跳过大部分扫描和分类,也不会发出任何邮件。
这正是我要的。Worker 收到邮件后拿到原始字节,用 OAuth 授权调用这个接口,把邮件写进 Gmail 收件箱。整个过程没有 SMTP 投递,没有 SPF、DKIM 要过,Gmail 也不会像对待外来邮件那样审查它,拒收和进垃圾箱都无从谈起。
写进去的是完整的原始邮件,邮件头、正文和附件都在,在 Gmail 里打开和正常收到的邮件一样。邮件时间取自它自己的 Date 头,在收件箱里的排序也是对的。
Synopsis
按这个思路,我从零写了 Synopsis,运行在 Cloudflare Workers 上,已经开源:
Loading repository data...
它把域名邮件完整备份到 Gmail,同时在 Telegram 私聊里发一条中文摘要。
flowchart TD
A[域名收到邮件] --> B[Cloudflare Email Routing 交给 Worker]
B --> C[Gmail API 写入原始邮件]
C -- 成功 --> F[AI 生成中文摘要并推送到 Telegram]
C -- 失败 --> D[依次尝试 Cloudflare Email Sending 和 Resend 发送原件]
D -- 成功 --> F
D -- 都失败 --> E[Telegram 告警,Cloudflare 稍后重新投递]
Telegram 里能做什么
每封邮件到了之后,Bot 会发来一条摘要:
- 第一行写着这封邮件发到了哪个地址。给不同网站注册时用不同的别名,一眼就能看出是哪家来的信。
- 验证码邮件会把验证码单独放一行,点一下就能复制。
- 其他邮件是一段 30 到 60 字的总结,最后一行用 🟢、🟡、🔴 标出需不需要处理、有多急。邮件里有退订入口的,还会顺带提醒可以退订。
- 点「原文」可以在 Telegram 里直接看正文,看完点「返回摘要」切回来。
- 点「删邮件+消息」会把 Gmail 里的这封邮件移进垃圾箱,同时删掉这条 Telegram 消息,适合处理用完就没用的验证码邮件。
摘要可以用三种 AI 服务,任选一种:OpenAI 兼容接口、OpenRouter 和 Gemini。默认提示词会把描述性的内容都转成简体中文,验证码、金额和单号这类需要原样复制的内容保持不变。
备份优先
写这个项目时,我让原件备份排第一,摘要和通知晚一点也没关系。
- Gmail 写入失败时(比如授权过期),会依次尝试 Cloudflare Email Sending 和 Resend,往另一个邮箱发一封带
original.eml附件的邮件,同时在 Telegram 告警。这两个兜底都是可选项,但建议至少配一个。 - AI 出错或者 Telegram 发送失败,都不会影响已经完成的备份。摘要失败时 Bot 会发一条占位消息,说明原件已经备份。
- 通知没发出去的邮件,定时任务每 5 分钟检查一次,按记录的状态补发。
兜底走的还是发信那条路,只在 Gmail API 出问题时临时顶上,日常备份靠的一直是 Gmail API 写入。
部署
完整步骤和每个配置项的说明都在仓库的 README 里,这里只讲大致流程和几个容易踩坑的地方。
准备
- 一个接入 Cloudflare 的域名,会用到 Email Routing、Workers 和 KV
- 一个用来收备份的 Gmail 账号,以及你自己在 Google Cloud 创建的 OAuth 客户端
- 一个 Telegram Bot
- OpenAI 兼容接口、OpenRouter、Gemini 三选一的 API key
- Node.js 22 以上和 pnpm
1. 安装依赖,创建 KV
git clone https://github.com/lainbo/synopsis.git
cd synopsis
pnpm install --frozen-lockfile
cp wrangler.example.jsonc wrangler.jsonc
cp .dev.vars.example .dev.vars
pnpm exec wrangler login
pnpm exec wrangler kv namespace create MAIL_KV
pnpm exec wrangler kv namespace create MAIL_KV --preview把得到的两个 KV ID 填进 wrangler.jsonc。
2. 拿到 Gmail 的 refresh token
这是整个部署里最麻烦的一步。
- 在 Google Cloud Console 新建项目,启用 Gmail API。
- 在 Google Auth Platform 里配置应用,受众选 External,然后把发布状态切到 Production。一直停在 Testing 状态的话,refresh token 通常 7 天就会过期,备份也就跟着断了。
- 创建 Web application 类型的 OAuth 客户端,授权重定向 URI 填
https://developers.google.com/oauthplayground。 - 打开 OAuth Playground,在右上角的设置里勾选 Use your own OAuth credentials,填入刚才的 client ID 和 client secret,Access type 选 Offline。
- 在 Step 1 填入下面两个 scope 并授权,账号选用来收备份的那个 Gmail:
https://www.googleapis.com/auth/gmail.insert
https://www.googleapis.com/auth/gmail.modify- 在 Step 2 点 Exchange authorization code for tokens,保存
refresh_token。
gmail.insert 用来写入邮件,gmail.modify 用来查询邮件状态和移进垃圾箱,Telegram 里的删除按钮靠的就是它。
3. 创建 Telegram Bot
在 BotFather 里用 /newbot 创建 Bot,保存好 token。然后用自己的账号给 Bot 发一条 /start,再读出自己的私聊 ID,仓库的 Telegram 设置文档 里有现成的脚本。最后随机生成一串字符作为 TG_WEBHOOK_SECRET,只能用字母、数字、下划线和短横线。
4. 写入 Secrets,选择 AI 服务
生产环境的密钥都用 wrangler secret put 写入,不要写进配置文件:
pnpm exec wrangler secret put GMAIL_CLIENT_ID
pnpm exec wrangler secret put GMAIL_CLIENT_SECRET
pnpm exec wrangler secret put GMAIL_REFRESH_TOKEN
pnpm exec wrangler secret put TG_BOT_TOKEN
pnpm exec wrangler secret put TG_CHAT_ID
pnpm exec wrangler secret put TG_WEBHOOK_SECRETAI 服务在 wrangler.jsonc 的 vars 里选择。以 Gemini 为例:
{
"SUMMARY_PROVIDER": "gemini",
"GEMINI_MODEL": "你要用的模型 ID"
}再写入对应的密钥:
pnpm exec wrangler secret put GEMINI_API_KEY选 OpenAI 兼容接口或 OpenRouter 时,把变量名换成 OPENAI_* 或 OPENROUTER_* 就行。用第三方的 OpenAI 兼容服务,还要填 OPENAI_BASE_URL。
兜底发信(Cloudflare Email Sending、Resend)的配置见 README 的「邮件兜底」一节。
5. 部署,接上 Telegram 和邮件路由
pnpm run deploy部署完成后:
- 按 README 注册 Telegram webhook,地址是
https://<你的 Worker 地址>/telegram/webhook,secret_token填刚才的TG_WEBHOOK_SECRET。 - 在 Cloudflare 控制台打开域名的 电子邮件路由,把 Catch-all(或者某个指定地址)的操作改成 发送到 Worker,选择
synopsis。 - 往域名邮箱发一封普通邮件和一封带附件的邮件,确认 Gmail 收件箱里有原件、附件能打开,Telegram 收到了摘要,「原文」和「返回摘要」都能用。
使用前要知道的
- 只支持一个 Gmail 账号,Telegram 也只能本人私聊使用。
- Telegram 里的原文是纯文本,长邮件会截断,完整排版和附件要去 Gmail 看。
- 生成摘要需要把邮件内容发给你选的 AI 服务,验证码之类的敏感信息也在里面,选服务商时要考虑这一点。
- 正文缓存默认保留 7 天,过期后 Telegram 里的按钮就用不了了,邮件本身还在 Gmail 里。
- 「返回摘要」会重新调用一次 AI,会产生额外费用。
总结
换成 Synopsis 之后,域名邮件都会出现在 Gmail 收件箱里,包括之前被拦下的 Steam 邮件。Telegram 负责第一时间告诉我来了什么邮件、要不要处理,Gmail 负责长期保存。
如果你也在用 Cloudflare Email Routing 转发到 Gmail,又被垃圾邮件或者转发失败困扰,可以试试这个项目。