用 TypeSafe 的 Jev 模型给 Gmail 邮件自动分类:判断每封邮件属于哪一类、要不要你亲自处理、多紧急, 先出报告给你过目,确认后再批量写回 Gmail 标签。附带钓鱼检测、退订分析、Gmail 过滤器生成, 以及一个 Chrome 侧边栏面板,不用敲命令也能用。
- 只加标签,不删邮件:代码里没有任何删除调用;每次写回都记日志,可一键撤销
- 先看再改:所有会改 Gmail 的操作都有预演模式和确认步骤
- 数据不出本机:邮件只在你电脑上处理,只把主题和摘要发给 Jev 做分类
- 很便宜:约 900 封邮件全量分类 ≈ $0.06
平台:核心脚本在 macOS / Linux / Windows 上都能跑。「每天自动整理」「服务开机自启」 和系统通知用的是 macOS 的 launchd 和 osascript,只支持 macOS。
| 功能 | 脚本 | 会改 Gmail 吗 |
|---|---|---|
| 拉取邮件(只读 header 和摘要) | fetch.py |
否 |
| Jev 分类 + 优先级打分 | classify.py |
否 |
| 分类报告 | report.py |
否 |
| 写回标签 / 撤销 | apply.py / undo.py |
是,可撤销 |
| 钓鱼检测 | phish.py |
否 |
| 退订分析(出清单,不替你点) | unsubscribe.py |
否 |
| 把固定发件人编译成 Gmail 过滤器 | rules.py |
--apply 时是,可撤销 |
| 每天自动跑一轮,有要紧事才通知 | daily.py |
是,可撤销 |
| Chrome 侧边栏面板的本地服务 | server.py |
按你点的操作 |
fetch.py → classify.py → report.py → apply.py
拉邮件 Jev 分类 出报告 写回标签(可 undo.py 撤销)
需要 uv(Python 版本和依赖它会自动装好):
git clone <这个仓库的地址>
cd <仓库目录>
uv sync- 打开 https://console.cloud.google.com ,新建一个项目
- 「API 和服务」→「库」→ 搜 Gmail API → 启用
- 「OAuth 同意屏幕」→ 用户类型选外部 → 把你自己的 Gmail 地址加进「测试用户」(不加会报 403)
- 「凭据」→ 创建 OAuth 客户端 ID → 应用类型选桌面应用
- 下载 JSON → 重命名为
credentials.json→ 放到项目根目录
权限是 gmail.modify:能读邮件、能打标签,不能永久删除邮件。授权令牌存在本地
token.json,不会上传到任何地方。
应用处于「测试」状态时,Google 的授权大约 7 天过期,到时重新跑一次会再弹浏览器登录。
在 https://console.typesafe.ai/keys 创建一个 key,然后:
echo 'TYPESAFE_API_KEY=你的key' > .envcredentials.json、token*.json、.env、data/、out/ 都在 .gitignore 里,不会被提交。
# 先拿 20 封试试分类准不准(几乎不花钱)
uv run src/fetch.py --limit 20 # 首次会弹浏览器授权
uv run src/classify.py
uv run src/report.py # 看 out/report.csv
# 满意了再全量
uv run src/fetch.py
uv run src/classify.py # 约 900 封 ≈ 3 分钟、$0.06
uv run src/report.py
# 确认分类准了再写回
uv run src/apply.py --dry-run # 预演:只打印要做什么,不碰 Gmail
uv run src/apply.py --apply # 真正写回
uv run src/undo.py out/applied-<时间戳>.jsonl # 后悔了就撤销以后定期增量更新:
uv run src/fetch.py --since 7d && uv run src/classify.py && uv run src/apply.py --apply不想敲命令,就在浏览器侧边栏里点按钮。扩展只管界面,活还是上面这些脚本干, 所以要先开着本地服务:
uv run src/server.py --install # 推荐(macOS):装成开机自启,装一次以后不用管
uv run src/server.py --status # 看服务在不在跑、日志尾部
uv run src/server.py --uninstall
uv run src/server.py # 或者临时手动开,监听 127.0.0.1:8765
uv run src/server.py --open # 不装扩展也行:直接在浏览器里打开同一个面板装扩展:Chrome 打开 chrome://extensions → 右上角开「开发者模式」→
「加载已解压的扩展程序」→ 选项目里的 extension/ 目录。之后点工具栏图标打开侧边栏。
| 页 | 能做什么 |
|---|---|
| 今天 | 今天要办几封、最要紧的几封(点开直达 Gmail)、可疑邮件提醒;「再整理一轮」 |
| 本轮结果 | 分类分布、可疑邮件、报告下载;待你确认:模型拿不准的邮件点 ✓ 认可或改类;底部一键写回 |
| 工具 | 钓鱼检测、退订分析、编译过滤器、撤销;单步跑每个环节 |
| 记录 | 每天自动整理的开关和时间;历次写回一键撤销;最近运行,失败的能看日志 |
「再整理一轮」旁边的只预演默认勾着:跑一遍但不改 Gmail,看完结果再点「写回 Gmail」。 跑任务时有分步进度和实时日志,可以停止,也可以放后台、跑完弹系统通知。
「待你确认」的判断存在 data/overrides.json,和分类结果分开放 —— 重跑
classify.py --force 不会冲掉你的判断。
本地服务的护栏:
- 只听
127.0.0.1,只接受扩展和它自己页面的请求 —— 你逛的其他网站没法借浏览器 偷偷让它跑任务(校验 Origin 和 Host,防 DNS rebinding) - 会改 Gmail 的操作界面上先弹确认,服务端也强制要求确认
- 同一时间只跑一个任务;撤销只接受
out/里真实存在的日志文件
Jev 不是 LLM,它不生成文本,只返回带概率的类型化判断(Choice / Score)。所以:
| 环节 | 谁做 |
|---|---|
| 拉邮件、去重、拼输入 | Python 代码 |
| 「属于哪一类 / 要不要我处理 / 多急」 | Jev |
| 阈值、打不打标签、打哪个 | Python 代码(taxonomy.yaml 的 policy) |
好处:改阈值、换策略不需要重新推理 —— 完整概率分布都存在 data/classified.jsonl 里。
按发件人(不是按邮件)评估哪些订阅能退,产出分档清单和退订链接:
uv run src/unsubscribe.py # 分析带退订链接的发件人
uv run src/unsubscribe.py --reuse --html # 复用判断 + 生成能点的本地网页($0)复用已有的分类结果,只对每个发件人多问三个问题:能不能退、是不是你真实持有账号的服务商、
内容本身值多少。分四档写进 out/unsubscribe.csv:
| 档 | 含义 |
|---|---|
| 🟢 建议退订 | 退了不会后悔 |
| 🟡 人工判断 | 模型拿不准,或者你其实在读 |
| 🔴 小心 | 有账号关系,退营销可能连带退掉通知 |
| ⚪ 保留 | 你在读这些 |
为什么「平均优先级低」不等于能退:银行、券商的营销邮件平均优先级可能很低,但那是你真实开户的 服务商,而且它们的营销邮件和交易通知常常来自相近的域名。所以除了「值不值得读」,必须单独问 「是不是你真实持有账号的服务商」—— 这一档一律不进「建议退订」。
三道硬兜底写在 taxonomy.yaml 的 unsubscribe.policy 里,优先于模型判断:
- 有账号关系概率 ≥ 0.40 → 降级
- 平均优先级 > 0.60 或最高优先级 > 2.0 → 降级
- 未读率 < 0.35(说明你每封都打开了)→ 降级到人工判断
--html 产出的 out/unsubscribe.html 刻意是本地文件而不是在线页面 —— 退订链接里带着邮箱
标识和一次性令牌,传给任何第三方都等于泄出去。脚本不会替你点退订:那是对外部服务的不可逆
操作,你自己看过再点。
有些发件人几乎总是同一类(≥3 封、纯度 ≥90%、置信度 ≥0.75)。这种「看发件人就够了」的情况 不需要每次都花钱推理 —— 编译成 Gmail 原生过滤器:新邮件一到就有标签,秒级、零成本。
uv run src/rules.py # 预览(不碰 Gmail)
uv run src/rules.py --xml # 产出 out/filters.xml,手动导入
uv run src/rules.py --apply # 直接用 API 创建(需要多授权一次)
uv run src/rules.py --undo out/filters-applied-<时间戳>.jsonl| 权限 | 怎么用 | |
|---|---|---|
--xml |
不需要额外权限 | Gmail → 设置 →「过滤器和屏蔽的地址」→ 底部「导入过滤器」 |
--apply |
需要 gmail.settings.basic |
会再弹一次浏览器授权,存在 token-settings.json |
过滤器只加标签,不归档、不删除、不标已读;你自己的邮箱地址会被自动排除。
过滤器只对今后的邮件生效,已有邮件的标签由 apply.py 负责。
header 归代码判,内容归 Jev 判:
uv run src/phish.py # 只对可能被伪装的分类跑推理
uv run src/phish.py --hard-only # 只跑 header 硬校验($0)
uv run src/phish.py --reuse # 复用上次推理,只重算阈值($0)代码这一半读 Gmail 已经验好的 spf / dkim / dmarc,再查域名仿冒:
- 形近字:
rn冒充m、1冒充l、vv冒充w——rnicrosoft.com和microsoft的编辑距离是 2,直接比抓不到,得先归一化 - 改字符:容忍度随品牌名长度走,短名字差两个字符往往就是另一家真实公司
- 品牌名 + 信任词:
paypal-secure.com。光「包含品牌名」不够 ——amazonaws.com就是 Amazon 自己的 - 同品牌换后缀不算仿冒:
amazon.de/amazon.co.jp都是人家自己的域名
可信域名不是写死的品牌榜,而是从你收件箱自己的历史里长出来的:反复出现且 dmarc 通过的 域名就是可信域名。
Jev 这一半只看主题和正文开头,回答三件 header 看不出来的事:这封信自称是谁、有没有在制造 紧迫感、有没有在骗你点进去输东西。输入里刻意不放 header 校验结果 —— 两条证据各自独立, 互相印证才有价值。
每天自动跑一轮,只在真有要紧事时才弹通知 —— 一个每天都响的提醒等于没有提醒。
uv run src/daily.py # 手动跑一轮
uv run src/daily.py --dry-run # 不写 Gmail、不弹通知,只打印
uv run src/daily.py --install # 装成 launchd 定时任务,每天 9:00
uv run src/daily.py --install --at 8:30
uv run src/daily.py --status
uv run src/daily.py --uninstall串起 fetch --since 7d → classify(已分类的自动跳过)→ apply --apply → phish --reuse,
把新出现的「⚡ Today 今天办」和可疑邮件合并成一条系统通知,同一封不会天天提醒。
任一步失败就中断、不推送 —— 半截的数据写回 Gmail 比不写更糟。
⚠ 它会自动写回 Gmail 标签(只加标签、可撤销)。不想自动写就用 --dry-run,或者别装定时任务。
全部改 taxonomy.yaml,不用动代码:
categories—— 加 / 删分类、改判断标准。描述要用英文写(Jev 的英文能力强于中文), 可以嵌中文线索词(日报 / 验证码 / 订单)帮模型对上号policy—— 置信度阈值、归档条件、「需要你处理」清单的入选条件rename_labels—— 想把你 Gmail 里原有的标签并进这套分类时才填(默认空,不动你的标签)
改完重跑 uv run src/classify.py --force(几分钱)。
- 低置信度(< 0.6)和
other一律不打标签,留给你人工确认 - 默认只加标签,不移出收件箱;要归档得显式
--archive - 每次写回都记
out/applied-<时间戳>.jsonl,undo.py可完整回滚 - 代码里没有任何
trash/delete调用 - 只读 header 和 Gmail 给的正文摘要(snippet),不读完整正文,附件不碰
- 发给 TypeSafe 之前遮掉验证码(独立的 4~8 位数字,见
src/redact.py);金额、年份、订单号保留。实测分类结果几乎不受影响
Jev 按输入 token 计费($0.042 / 百万),输出免费。每封邮件约 1600 token。
| 规模 | 成本 |
|---|---|
| 20 封试用 | ≈ $0.001 |
| 约 900 封全量 | ≈ $0.06 |
| 每周增量 50 封 | ≈ $0.003 |