一句话结论:用 agently-cli message +watch 长轮询替代定时轮询,配合生产者-消费者解耦,Agent Mail 新邮件能做到近实时推送到飞书和微信。
6 月写过一篇《Agent Mail 跟普通邮箱到底有什么区别》,当时留了个尾巴——邮箱能收能发,但怎么做到「来邮件就立刻知道」?QQ 邮箱的 Agent Mail 直到 11 月 12 日升级 CLI 到 1.0.18 才给出答案:message +watch,服务端长轮询。
对于一些监听告警的一些邮件需要实时监听邮件做处理,这篇文章记录从方案验证到跑通推送的完整过程。
之前的方案:每 30 分钟轮询一次
7 月初我设计过一版监控方案,思路很直接:
Cron (每30分钟)
→ agently-cli message +list --is-unread
→ 有未读? → +read 获取全文
→ AI 分类 → 推送到 飞书
能用,但问题也很明显:
- 最坏情况延迟 30 分钟——邮件来了你可能半天看不到
- 大量无效请求——每 30 分钟查一次,99% 的时候返回空列表
- AI 分类要吃 token——每次 cron 都过一遍模型
后来因为标记已读、去重这些问题没解决,这个方案一直搁着。
转折点:+watch 长轮询
11 月 12 日 agently-cli 升到 1.0.18,新增了 message +watch 命令。跟 +list 的区别是:
+list:客户端主动拉,问一次答一次+watch:客户端发起请求后,服务端保持连接不挂断,有新邮件才返回
这跟 IMAP IDLE 是一个思路。区别在于 +watch 是 agently-cli 封装好的,不用你自己处理协议层的东西。
操作过程
安装agently-cli
注册QQ的 Agent Mail,现在处于内测状态,每人最多 2 个地址、1G 存储、每天发 50 封、单附件 1MB,轻量够用。然后让Hermes安装最新的 agently-cli
`请阅读 https://agent.qq.com/doc/cli-setup.md 文档,按照步骤为我安装并配置 Agent Mail CLI。`
编写脚本监听邮件
帮我在Hermes中添加长轮询监听(agently-cli message +watch)Agent Mail时实邮件,收到新邮件的时候,把邮件内容发送到飞书和微信渠道上来。

架构设计:生产者-消费者解耦
agently-cli message +watch (长轮询,实时)
└─agent-mail-watch.py (后台常驻)
└─ agently-cli message +watch (长轮询)
└─ 新邮件 → agent-mail-queue.jsonl (NDJSON 队列)
agent-mail-consumer.py (每 10秒~1 分钟)
└─ 检查队列 → 去重 → 推送到飞书 + 微信
拆成两段的理由:
- 守护进程只负责写队列——它的工作就是盯着
+watch输出,看到新邮件就追加一行 JSON 到队列文件。不做任何业务逻辑。 - cron 只负责读队列投递——每分钟跑一次,检查队列里有没有未处理的邮件,有就格式化输出到渠道。
两边互不阻塞。守护进程挂了不影响 cron 读已有队列;cron 挂了不影响守护进程继续收邮件。重启后从队列文件接着处理,不会丢邮件。
为什么不让 cron 直接调 agently-cli?
Hermes 的 API Server(端口 8642)只有 /health 端点,没有邮件投递接口。cron 跑的是 isolated session,没法直接操作 agently-cli 的长轮询进程。只能靠「文件队列」这个中间介质传递消息。
为什么不用hermes webhook方案
用webhook的话,需要启用webhook,还得让agent-mail-watch.py 去 POST 到 localhost:8644/webhooks/xxx,这跟直接调 API 没区别。
| JSONL + cron | webhook 方案 | |
|---|---|---|
| 依赖 | 无,已运行 | 需启用 webhook + 重启 gateway |
| 延迟 | ≤1 分钟 | 理论实时 |
| 复杂度 | 两个文件,已验证 | 需新建订阅 + 适配 POST |
| 可靠性 | 守护进程 + cron 双保险 | 依赖 gateway 稳定 |
当前方案(JSONL + cron)能稳定运行,推送延迟 10秒 ~1 分钟对邮件场景完全够用。
踩坑记录
坑 1:readline() 阻塞事件循环
第一版 Python 守护进程用了 proc.stdout.readline()。看起来没问题,实际跑起来发现:+watch 在等邮件时不会有任何输出,readline() 就一直挂着。Python 的单线程事件循环被卡死,后面的邮件处理逻辑根本执行不到。
改成 for line in proc.stdout 就好了。这是 Python 迭代文件对象的正确姿势——内部用缓冲读取,不会因为等待而阻塞整个进程。
坑 2:长轮询挂起≠卡死
第一次启动 +watch 时,进程显示"卡住不动了"。当时以为出了 bug,差点 kill 掉。后来确认:**+watch 在没新邮件时,服务端会保持 HTTP 连接不返回**,这是正常行为。有新邮件时才会解除挂起,返回数据。
关键验证方式:手动发一封测试邮件,看进程是否恢复输出。结果证明 for line in proc.stdout 能正确处理这种挂起/恢复的节奏。
文件清单
| 文件 | 用途 |
|---|---|
/root/.hermes/scripts/agent-mail-watch.py |
守护进程,运行 +watch,写入队列 |
/root/.hermes/scripts/agent-mail-consumer.py |
消费者,读队列并格式化输出 |
/root/.hermes/cron/agent-mail-queue.jsonl |
新邮件队列(NDJSON 格式) |
/root/.hermes/cron/agent-mail-processed.json |
已处理 message_id 列表(去重用) |
/root/.hermes/cron/agent-mail-watch.log |
守护进程日志 |
关键决策
1. 不用定时轮询代替长轮询
定时轮询的问题前面说了——延迟高、浪费请求。+watch 是服务端推的,延迟接近 0。唯一的前提是 agently-cli 得支持长轮询,现在 1.0.18 已经支持了。
2. 投递走 no_agent cron
Hermes 的 cron job 有个 kind 字段,设成 no_agent 就不会调用大模型。consumer 脚本只做格式化输出,不需要 AI 参与,用 no_agent 省 token。
3. 队列文件做持久化
/root/.hermes/cron/agent-mail-queue.jsonl 用 NDJSON 格式存邮件。守护进程追加写入,cron 读取后标记已处理。这样即使 cron 挂了或者服务器重启,队列里的邮件不会丢,下次启动后接着处理。
实际效果
实时给自己监听的Agent Mail邮箱发封测试邮件:

从邮件到达 agently-cli 服务端,到飞书/微信收到推送。脚本本身极轻——agent-mail-consumer.sh 只是读一个 JSONL 文件、做 JSON 解析和去重,单次执行不到 1 秒。所以缩短间隔的 CPU/IO 开销几乎可以忽略。可能根据实时性设置10秒~1分钟的间隔来获取邮件。
说说我的用法
现在监听了 Cloudflare 的 预算报警邮件,然后收到邮件后,在agent-mail-consumer.sh脚本中添加一下收到cloudflare邮件后调用 cloudflare cli 把对应的Token删了,这样Cloudflare 使用R2 超了也不会超太多了。后续如果 Hermes 那边要做 A2A 通信(Agent 之间自动发邮件),这个实时通道就能派上用场——对方 Agent 发来邮件,你这边秒级收到通知,不用再靠人肉刷邮箱。
评论区