Skip to content

Repository files navigation

MailRecon · 求职邮件对账与行动提醒

一个只读的本地邮箱代理:把你的求职邮件,变成聊天软件里一张能一键完成的行动清单。

Read-only IMAP · Local-first · PII-masked before LLM · Interactive cards


它解决什么问题

秋招投了五六十家公司之后,邮箱会变成一团乱麻:

  • 我到底投了哪些公司?哪些是主动投的,哪些是内推?
  • 谁回我了?谁已读不回?谁的邮件被我漏掉了?
  • 有没有"需要我在某个日期前做点什么"的邮件,静静躺在收件箱里过期了?
  • 一封测评邀请里给了通行证号、一封面试通知里给了确认链接,三天后翻出来找不到了

普通邮件客户端不解决这些——它们是"收信工具",不是"办事工具"。

MailRecon 只做一件事:盯着你的邮箱,把"需要你动手的事"抽出来,变成一张能点一下就完成的任务卡,推到飞书上。

长什么样

每条待办一个「✅ 完成」按钮,点完可以撤销:

📋 求职任务 · 已完成 2/8
▰▰▱▱▱▱▱▱ 6 待办 🔥 24h 内 1

🔥 24 小时内截止(1)
1. 完成星辰科技在线测评,点击链接作答
 星辰科技邀你参加 2027 校招在线测评
 📍 点邮件里的测评链接作答 · ⏰ 09-25 16:50 · 🔗 bs.example.com
  [ ✅ 完成 ]
  [ 🔗 去做 ]

📌 进行中(7)
2. 重投示例工程师:原信未送达 hr@acme.example.com
 你投给 hr@acme.example.com 的邮件没送到:该地址不存在
 📍 邮箱搜 postmaster@qq.com 看原始退信 · 📭 邮件里没写截止时间
  [ ✅ 完成 ]

点「✅ 完成」之后会回来一张确认卡,上面带一个「↩️ 撤销」,误点可以直接反悔。

字数不多,但每条待办都回答四个问题:谁 · 发生了什么 · 要做什么 · 去哪做。

核心设计

1. 只读,是刻意的选择

MailRecon 从不修改你的邮箱。整个代码库里没有任何一处标记已读、移动、删除、回复或投递。

具体做法:

  • IMAP 用 EXAMINE 而不是 SELECT —— 只读方式打开邮箱
  • 取正文用 BODY.PEEK[] 而不是 BODY[] —— BODY[] 会顺带把邮件标记成已读,PEEK 不会
  • 增量同步靠 UID + UIDVALIDITY 水位线,不靠"删除标记"之类有副作用的操作

这不是能力缺失,是设计取舍:一个会动你邮箱的助手,你不敢让它常驻。

2. 它是对账工具,不是邮件客户端

大多数邮件工具处理的是"一封信"。MailRecon 处理的是一次投递的生命周期:

我发出的申请  ──┐
                ├──►  对账  ──►  已回复 / 已退信 / 无回音
对方回来的信  ──┘

它分别读收件箱和已发送,按"公司在域名层面"配对——hr@acme.example.com 和 campus@acme.example.com 算同一家;但 10000@qq.com 这种公共服务邮箱算独立个体,不会因为同域就被误判成 HR 回复。

3. 脱敏在本地做完,才送给模型

邮件里全是隐私:姓名、手机号、证件号、学校、住址。

这些在你机器上用规则先抹掉,才发去调模型(mask.py)。同时保留对任务有用的信息——日期、金额、编号、通行证——否则提取出来的待办就没用了。

还有一道独立护栏:身份配置文件没加载成功时,整个分析环节直接拒绝调用模型,而不是"降级成不脱敏地发出去"。

送模型之前还会先做本地分诊(triage.py):把邮件分成 drop / template / llm / local_only 四类。实测 999 封收件箱邮件里,94% 不需要模型参与就能判掉(营销、验证码、平台通知)。既省钱,也少暴露。

4. 能力边界:只提醒,不代做

这套系统不会替你打开测评链接、不会替你提交表单、不会替你回复邮件。

原因很实际:那些链接大多是一次性的、带签名的,而且服务端会拦截脚本 UA——实测某招聘系统对 curl 直接 302 到拦截页,对浏览器 UA 放行 200。与其做一个注定失败的自动化,不如把"该做什么"准确地送到你手上,让你自己点。

5. 成本是有护栏的

  • 每天最多 50 次模型调用,硬上限
  • 单轮最多处理 20 封
  • 本地分诊先过滤掉约 94% 的邮件

三个数字合起来:即使邮箱被轰炸,账单也是封顶的。

架构

                    ┌──────────── 每小时 ────────────┐
                    │                                │
  QQ 邮箱 ◄──只读 IMAP──► sync.py ──► reconcile.py ──► triage.py
                    │        取信        对账          本地分诊
                    │                                │
                    │                            analyze.py
                    │                      脱敏 → 调模型 → 提取字段
                    │                                │
                    │        ┌───────────────────────┤
                    │        │                       │
                    │   push_actions.py          taskboard.py
                    │    行动项卡片                任务台账
                    │        │                       │
                    └────────┴──────► 飞书 ◄─────────┘
                              ▲          │
                        poller.py ◄──────┘
                   处理「完成 3」/ 按钮回调

取信/分析/推送走小时级批量;你的操作(点按钮、发指令)走秒级。两条线分开,所以点一下按钮是即时的,不用等下一轮。

目录结构

mail-agent/             取信 → 对账 → 分诊 → 分析 → 推送
  sync.py               增量取信(EXAMINE + BODY.PEEK,UID 水位线)
  reconcile.py          投递对账
  triage.py             本地分诊(省掉 94% 的模型调用)
  analyze.py            脱敏 → 调模型 → 落库
  prompt_final.py       提取用的 prompt 与输出校验
  mask.py               本地脱敏
  links.py              从原始正文里挑"该点的那一个"链接
  push_actions.py       行动项卡片推送
  taskboard.py          任务台账视图
  notify.py             回复 / 退信通知
  feishu.py             飞书 API 客户端
  run.sh / daemon.sh    小时级调度

mail-agent-interact/    交互层
  poller.py             指令泵:轮询 + 校验 + 执行 + 回执
  cmds.py               指令解析(完成 / 放弃 / 撤销 / 状态 …)
  board.py              常驻看板卡
  infer.py              从后续邮件推断任务是否已完成
  fsapi.py              飞书收发(含卡片原地刷新)

hermes/                 聊天网关侧的桥接(可选)
  task-cmd-bridge/      把飞书卡片回调转成指令
  skill-求职台账/        让 AI 助手能读懂脱敏后的台账

几个踩过的坑(写给想自己搭的人)

都是实测踩出来的,不是理论:

cron 在这个环境里不触发。 WSL 里 cron 进程活着、crontab 也配对,但整点任务和每分钟探针都不执行。最后换成了常驻循环脚本(daemon.sh)。并且:脚本必须自己在每次执行前检查可执行位——我们遇到过一次 chmod 丢失,导致守护进程每小时都被拒绝执行、错误只进 stderr、日志一片安静,系统静默停摆两个半小时才发现。

UID n:* 的语义有个坑。 按 RFC 3501,当邮箱里的最大 UID 低于你请求的起始 UID 时,搜索仍会返回最后一封邮件。不客户端过滤的话,你会反复处理同一封信。

卡片按钮回传的不是消息 ID。 飞书的卡片回调给的是回调 token,拿它去查消息会 404。所以卡片点击需要一条独立的信任通道,不能复用"拿 message_id 回飞书核对正文"那套强校验。

卡片只能改 14 天内的消息。 刷新历史看板时超过 14 天的会失败,得按"单张失败忽略、全失败才发新卡"处理。

一次性链接会在提交瞬间失效。 招聘系统的测评/简历链接大多带签名,提交后服务端立刻作废。所以"链接打不开"经常不是链接错了,而是你已经做完了。

过滤和截断的顺序会咬人。 我们写过 list[:2] 再过滤条件,结果前两条里只要有一条不满足条件,真正符合条件的第三条就永远轮不到。看起来能跑,实际是静默少功能。

快速开始

这套东西是为个人环境部署的,不是开箱即用的 SaaS。路径、飞书自建应用、模型接口都需要按你的环境改。

# 1. 配置
cp .env.example .env                      # QQ 邮箱(用授权码,不是登录密码)+ IMAP 地址
cp identity.example.json identity.json    # 你的身份信息,用于本地脱敏,不会上传

# 2. 依赖
pip install -r requirements.txt

# 3. 跑一轮(只读,不会改你的邮箱)
./mail-agent/run.sh

# 4. 常驻
./mail-agent/daemon.sh &                   # 每小时取信 / 分析 / 推送
./mail-agent-interact/poller_daemon.sh &   # 每 60 秒处理你的指令

QQ 邮箱的 IMAP 要用「授权码」,不是登录密码:设置 → 账户 → POP3/IMAP/SMTP 服务 → 生成授权码。这套访问方式是官方支持的;风控针对的是异常行为(高频重连、密码登录),不是 IMAP 本身。

已知限制

  • 路径是硬编码的(/root/mail-agent、/opt/mail-agent-interact),可移植性改造还没做
  • 只在 08:00–20:00 运行,晚上到的邮件要等第二天早上
  • 依赖飞书:想换别处(Telegram / 企业微信 / 邮件日报)需要改 feishu.py 那一层
  • 模型调用有额度,用完了当天不再分析
  • triage.py 的平台域名列表需要按自己的邮箱情况填(见 platform_domains.txt.example),内置只有示例

License

MIT

About

求职邮件对账与行动提醒 · 只读 IMAP → 本地脱敏 → AI 提取行动项 → 飞书交互卡片。Read-only, local-first mail agent that reconciles job-application emails and pushes actionable one-tap cards.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages