Xixixiao2007/dsh-mail-digest ↗★ 0

dsh-mail-digest

每轮回答结束后,把一条详略得当的中文摘要发到你的邮箱(QQ / 163 / 自建 SMTP),地址在 DSH 设置页里配置。 适合需要通过 QQ、163 或自建邮箱接收 DSH 会话摘要并支持回信的用户。

パッケージ
dsh-mail-digest
互換性
未検証
バージョン
0.1.0
ライセンス
MIT
最終更新
2026/09/23

インストール

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:Xixixiao2007/dsh-mail-digest

ドキュメント

README 全文を読む ↗

配置

设置页五块,填完点「保存」,再点「发送测试邮件」;要用回信功能就再点「测试收信」:

填什么说明
邮箱类型QQ / 163 / 126 / 自定义 SMTP
发件邮箱地址你的完整邮箱地址
授权码 / 密码不是登录密码。网页版邮箱 → 设置 → 账户 → 开启 IMAP/SMTP 服务后生成的授权码
收件地址留空 = 发给自己
回信通道打开「启用回信通道」才能回信(需要邮箱已开启 IMAP 服务)

点「发送测试邮件」验证发信,点「测试收信」验证 IMAP 登录(只验证登录,不读你任何邮件内容)。授权码只存在本机 %USERPROFILE%\.dsh\dsh-mail-digest\config.json,界面回显时永远是空的。

各邮箱的坑

邮箱注意
163 / 126① 登录后必须声明客户端身份(ID 命令),否则 SELECT 被拒:NO SELECT Unsafe Login。② 服务端主题搜索不可用:对 MIME 编码过的主题(=?gb18030?B?...?=),UID SEARCH SUBJECT "..." 一律返回空——所以插件改成取回候选邮件后在本地匹配主题。插件都已处理,你不需要做什么。
QQ回复长主题时会按显示宽度插入空格,把 [DSH-Q:x] 改成 [D SH-Q:x]。插件的本地匹配会压缩空白后再比对,所以不受影响;你也不要去手改主题。
全部授权码不是登录密码,要在网页版邮箱里单独生成,且必须先开启 IMAP/SMTP 服务。

回信落在哪个文件夹

插件每次收信会在一条连接内枚举文件夹,优先搜 INBOX 与「已发送 / Sent」这类, 以兜住「你自己回复自己的邮件被归到已发送」的情况(163 的「已发送」就是这种)。

为什么不用服务端主题搜索

踩过的坑(实测于 163):UID SEARCH ALL 能正常列出全部邮件,而 UID SEARCH SUBJECT "DSH-"、"DSH"、"[DSH-" 各种通配前缀形态 一律返回空。一旦依赖它,你的回信连候选集都进不来,后面客户端逻辑再对也没用 (等于在下游修、上游已筛空)。

所以收信流程是:UID SEARCH ALL 拿 UID 列表 → UID FETCH (FLAGS BODY.PEEK[]) 取回候选邮件 → 本地比对主题(压缩空白后子串匹配)→ 本地按 \Seen 判断未读。

候选范围怎么定(这里出过一个很隐蔽的 bug):早期实现是「取最近 limit*4 个 UID, 从最旧往最新扫,扫满 limit 封就停」。问题是你每收到一封摘要回一次,旧回信就会把 名额占满,你最新那封审批回信排在候选之外,于是「回了邮件批准,等很久毫无反应」—— 最后只能去界面点拒绝。修复分三层:

  1. limit 现在只限制返回条数,绝不在候选阶段截断(rankCandidateUids 是纯函数, 有专门的回归测试锁住这一点);
  2. 候选从最新往回排队 —— 回复是实时的,旧邮件沉底无所谓;
  3. 额外用我们已知的完整线程标记做服务端精确定位(我们发出的 token 是唯一随机串, 不受 MIME 编码影响),命中的排在最前,邮箱再大也不会漏。这一步是增强: 服务端匹配不到时自动退回 1、2 的宽松扫描。

成本由 window(宽松扫描看最近多少封,默认 max(limit*4, 40))一个旋钮控制。

处理完的回信会自动标为已读(2026-09-23 用户发现)

收信分两轮:先只看未读,再全量兜底。原实现里有一句"未读那轮有返回就跳过全量" (想省一次 IMAP 往返)—— 那是个会累积的缺陷:

未读积压起来后,"只看未读"那一轮的名额就被旧邮件占了, 用户新发的信反而进不了候选。用户当时的说法是"它忽然不认我的邮件了", 手动把邮件标为已读之后就恢复正常。

两处修正:

  1. 两轮都跑,不再因为未读轮有返回就跳过全量兜底 —— 多一次 IMAP 往返,换"永不漏读";
  2. 处理完的回信标为已读(UID STORE +FLAGS.SILENT (\Seen)), 让"未读"这个集合始终只包含真正还没处理的东西,不再无限堆积。

标记的判定刻意严格:只有真的把结论交出去了才标(审批已决、答案已采纳、 回信已投回会话)。认不出、发件人不对、References 不匹配、解析不出内容、 投回失败 —— 一律保持未读,让你能看见、能重试。标已读只该消灭"处理完的噪音", 绝不能吞掉"还没搞定的事情"。想关掉这个行为,在配置里设 reply.markProcessedAsRead = false。