用手机发邮件,让家里电脑的 AI 替你干活并回信
31.4/100
综合分
2
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/16 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add QIKO0721/dsh-mail-bridge该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件发起外部网络请求
查看扫描证据(共 29 处,展示前 21 条)
- 读取本地文件bin/cli.js:45
if (fs.existsSync(file)) { - 写入本地文件bin/cli.js:68
fs.writeFileSync(file, JSON.stringify(defaultConfig(), null, 2), 'utf8'); - 读取本地文件bin/cli.js:160
const found = pf.filter(f => { try { return fs.existsSync(f); } catch (e) { return false; } }); - 写入本地文件bin/cli.js:174
fs.writeFileSync(probe, 'ok', 'utf8'); - 删除本地文件bin/cli.js:175
fs.unlinkSync(probe); - 读取本地文件src/app.js:59
const raw = JSON.parse(fs.readFileSync(file, 'utf8').replace(/^ FEFF/, '')); - 写入本地文件src/app.js:61
fs.writeFileSync(file, JSON.stringify(raw, null, 2), 'utf8'); - 读取本地文件src/config.js:85
if (!fs.existsSync(file)) { - 读取本地文件src/config.js:91
raw = parseJsonLoose(fs.readFileSync(file, 'utf8')); - 读取本地文件src/dsh.js:171
content = fs.readFileSync(f, 'utf8').replace(/ /g, '\n'); - 读取本地文件src/dsh.js:177
parts.push('mtime:' + path.basename(f) + ':' + fs.statSync(f).mtimeMs); - 读取本地文件src/dsh.js:208
return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^ FEFF/, '')); - 写入本地文件src/dsh.js:212
try { fs.writeFileSync(file, JSON.stringify(obj, null, 2), 'utf8'); } catch (e) { } - 读取本地文件src/dsh.js:272
try { entries = fs.readdirSync(dir); } catch (e) { entries = null; } - 读取本地文件src/dsh.js:283
if (fs.statSync(full).isFile()) return full; - 读取本地文件src/dsh.js:290
if (fs.existsSync(full) && fs.statSync(full).isFile()) return full; - 读取本地文件src/logger.js:36
const st = fs.statSync(file); - 读取本地文件src/logger.js:38
const tail = fs.readFileSync(file, 'utf8').slice(-KEEP_BYTES); - 写入本地文件src/logger.js:39
fs.writeFileSync(file, tail, 'utf8'); - 写入本地文件src/logger.js:47
fs.appendFileSync(file, line + '\n', 'utf8'); - 发起网络请求src/mail.js:55
for await (const msg of client.fetch({ seen: false },
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
发封邮件,家里机器替你干活。收件箱和 DSH 之间的桥,专为 NAT 后面的电脑而设。
README
由 DeepSeek 最新模型翻译生成dsh-mail-bridge
把邮件当作 DSH 的命令通道。 用手机发一封邮件,家里机器上的 AI 干活,然后回信。NAT 后面也能用——不需要公网 IP,不需要端口转发,不需要隧道服务,不需要云账号。
中文说明 · 经验教训 · MIT 许可证
为什么用邮件
「在外面控制家里的电脑」,难的不是 AI,而是怎么够到一台没有公网 IP 的机器(家用宽带通常都在运营商级 NAT 后面)。
| 方案 | 需要公网 IP | 需要装东西 | 手机端 |
| 邮件中继(本项目) | 不需要 | 不需要——纯 Node | 系统自带邮件 App |
| 端口转发 | 需要 | — | — |
| frp / Tailscale / ngrok | 不需要,但要有中继主机 | 需要,两端都要装 | 需要 |
| 云函数 + webhook | 不需要 | 云账号 | 自己写脚本 |
邮件胜出,因为它老、无聊、到处都支持。你的机器去轮询自己的收件箱——只发起出站连接——所以外面不需要有任何东西能被访问到。而且你的流量不用交给第三方中继服务。
工作原理
邮箱助手 · 用一封邮件,遥控家里的电脑干活
59 秒概览,1280x720,无音频,字幕已烧录。
想要视频文件?docs/media/demo.mp4。
展开完整文档(README 剩余 1 节)
dsh-mail-bridge
把邮件当作 DSH 的命令通道。 用手机发一封邮件,家里机器上的 AI 干活,然后回信。NAT 后面也能用——不需要公网 IP,不需要端口转发,不需要隧道服务,不需要云账号。
中文说明 · 经验教训 · MIT 许可证
为什么用邮件
「在外面控制家里的电脑」,难的不是 AI,而是怎么够到一台没有公网 IP 的机器(家用宽带通常都在运营商级 NAT 后面)。
| 方案 | 需要公网 IP | 需要装东西 | 手机端 |
| 邮件中继(本项目) | 不需要 | 不需要——纯 Node | 系统自带邮件 App |
| 端口转发 | 需要 | — | — |
| frp / Tailscale / ngrok | 不需要,但要有中继主机 | 需要,两端都要装 | 需要 |
| 云函数 + webhook | 不需要 | 云账号 | 自己写脚本 |
邮件胜出,因为它老、无聊、到处都支持。你的机器去轮询自己的收件箱——只发起出站连接——所以外面不需要有任何东西能被访问到。而且你的流量不用交给第三方中继服务。
工作原理
邮箱助手 · 用一封邮件,遥控家里的电脑干活
59 秒概览,1280x720,无音频,字幕已烧录。
想要视频文件?docs/media/demo.mp4。
用 powershell -NoProfile -ExecutionPolicy Bypass -File scripts/make-video.ps1 重新生成这两个文件。
your phone your machine (behind NAT)
┌──────────────┐ ┌───────────────────────────┐
│ Mail app │ │ dsh-mail-bridge │
│ │ ── email ──┐ │ │ │
│ │ │ │ ▼ │
│ │ ◄─ reply ──┤ │ dsh --profile headless │
└──────────────┘ │ │ │ │
▼ │ ▼ │
┌─────────────┴─┐│ AI does the work │
│ Mail server ││ │
│ IMAP / SMTP ││ │
└───────────────┘└───────────────────────────┘
▲ poll INBOX (outbound)
└─ send reply (outbound)
1. 你给自己的地址发一封邮件。
2. 你的机器通过 IMAP 轮询收件箱。
3. 正文会被清理(去掉引用的回复和签名),然后交给 DSH。
4. DSH 的答案通过 SMTP 回信给你。
功能
- 纯 Node,三个依赖——imapflow、mailparser、nodemailer。没别的了。
- 多服务商——任何 IMAP/SMTP 服务商都能用;QQ 邮箱已经预配置好作为示例。
- 对话连续——复用 DSH 会话 id,所以你可以接着说「现在把那个脚本改一下」。
- 防回环——回信带一个标记头,永远不会被当成新命令。
- 防伪造 — 发件人白名单 + SPF/DKIM 校验 + 可选的共享密钥。
- 去除引用 — 识别 Gmail、Outlook、QQ/网易等的回复引用格式。
- 去除签名 — 识别自动追加的签名,这样即使你的邮件客户端多加了两行,new session 依然有效。
- 权限自愈 — 检测到 DSH 因权限拒绝某项操作时,自动在新会话中重试(见下文)。
- 抗崩溃 — unhandledRejection / uncaughtException 都会被捕获并记录;一封坏邮件不会拖垮整批任务,也不会拖垮进程。
- 日志轮转 — 日志文件上限 2 MB。
环境要求
- Node.js >= 18
- 一个开启了 IMAP/SMTP 的邮箱。 请获取应用专用授权码,而不是你的账号密码。QQ 邮箱:设置 → 账户 → 开启 IMAP/SMTP 服务。
- DSH(DeepSeek Harness)。 本项目自身不含任何 AI —— 它只是一座桥,把你的邮件交给 DSH。安装方式:
npm install -g @deepseek-ai/dsh
dsh --version # should print a version
这样你的 PATH 上就有了 dsh 命令,这座桥找的就是它。
(npm run doctor 会报告有没有找到。)
快速开始
git clone https://github.com/QIKO0721/dsh-mail-bridge.git
cd dsh-mail-bridge
npm install
npm run init # writes config.json, tells you exactly what to fill in
→ open config.json and set three fields:
email your mailbox address
authCode the app-specific authorization code (NOT your login password)
allowFrom the ONLY addresses allowed to command your machine
npm run doctor # environment check; does NOT connect to mail or run tasks
npm start # start polling
语言。 命令行、回复邮件和日志行都跟随你的系统区域设置(中文区域 → 中文,其他 → 英文)。想用别的语言可以覆盖:
node bin/cli.js --lang=en doctor # force English for this run
export MAIL_BRIDGE_LANG=zh # or set it for the whole shell
doctor 这一步值得仔细看 —— 它会检查 Node、依赖、你的配置、能不能找到 DSH、你的权限配置是否可监控,并针对每一项问题告诉你该怎么修。
然后给自己发一封邮件:
To: your-address@example.com
Subject: anything
Body: how much disk space is left on D:?
一个轮询间隔加上处理时间之内,你就会收到回复。
把它变成常驻服务
npm start 会占住运行它的那个终端。想让它一直跑:
- Windows — 把指向 npm start 的快捷方式(或一个运行它的 .cmd)放进 shell:startup,或者注册成登录时运行的计划任务。
- Linux/macOS — 写一个 systemd unit 或 launchd plist,或者用 pm2 / tmux / screen。
只要它一直在运行,就会一直轮询。如果机器休眠或断网,轮询就会失败并重试——如果回复不再到达,去查日志。
特殊命令
正文里只能放其中一条:
| 正文 | 效果 |
| help | 回复使用说明 |
| new session / new / reset | 清空记忆;下一封邮件重新开始 |
重置在下一封邮件生效,而不是携带命令的那封——那封回复不会调用 DSH。
配置
config.json 由 npm run init 创建,并且已加入 gitignore(里面存着你的授权码)。
{
"email": "you@example.com",
"authCode": "your-16-char-app-code",
"allowFrom": ["you@example.com"], // the ONLY addresses allowed to command this machine
"imap": { "host": "imap.qq.com", "port": 993, "secure": true },
"smtp": { "host": "smtp.qq.com", "port": 465, "secure": true },
"pollSeconds": 15, // how often to check the inbox (min 5)
"taskTimeoutSeconds": 600, // kill a task that runs longer than this
"maxTaskChars": 8000, // truncate very long bodies before handing them off
"maxReplyChars": 40000, // cap on the reply body
"maxPerPoll": 10, // most unread messages handled per poll cycle
"dshBin": "", // empty = auto-detect
"dshProfile": "headless",
"sessionId": "", // empty = each email is independent
"fromName": "Mail Bridge", // display name on replies, also the signature tag
"secret": "", // optional passphrase required in the body
"strictAuthCheck": true, // set false only if your own SPF setup fails
"stripSignatures": true // strip auto-appended signature blocks
}
值得解释的字段
| 字段 | 你可能改它的原因 |
| sessionId | 留空适合一次性命令。设置它(或者干脆让它自动填上)就能跨邮件保留上下文。 |
| fromName | 回复里显示的发件人名称。也用作每封回复末尾的标签。 |
| stripSignatures | 默认开启。很多邮件应用会自动追加「姓名 + 地址」块;剥掉它能让这些噪音不进任务文本,也不会破坏特殊命令。如果你想让这些行传进去,就关掉。 |
| strictAuthCheck | 要求外部投递的邮件通过 SPF。只有当你从自建服务器发信、且它确实过不了 SPF 时才关掉。 |
| taskTimeoutSeconds | 一个任务能跑多久,超过就杀掉它的 DSH 子进程。 |
| maxTaskChars | 正文超过这个长度会被截断并附上说明。如果你要发很长的提示词,就调高它。 |
| maxPerPoll | 一轮轮询处理多少条未读邮件。这是背压控制:没有它,积压的未读邮件会被串行消费,而一个任务可能要花一分钟,几百条未读邮件就会让轮询卡住几个小时,你还以为它在正常工作。剩下的会在下一轮被取走。 |
安全
这个程序让一封邮件可以命令一台电脑。请读这一部分。
| 授权码就是密码。 | 谁拿到它,谁就能读你的邮件、发你的邮件。绝不要提交到仓库,绝不要截图。 |
| 收紧 allowFrom。 | 它是主要关口。只保留你自己的地址。 |
| 发件人地址可以伪造。 | 所以光靠白名单不够——见下文。 |
| AI 可以执行命令。 | 它能读写文件、运行程序。这正是它的意义,也正是风险所在。 |
防伪造:为什么光看 From 不够
实测行为(QQ 邮箱;其他主流服务商类似):
| 邮件来源 | Authentication-Results 头 |
| 从服务商内部发出(网页 / 官方 App / 它自己的 SMTP) | 不存在 |
| 从外部服务器投递 | 存在——包含 spf= / dkim= 结果 |
所以规则是:
no Authentication-Results → internal delivery → allow
has Authentication-Results → external → require spf=pass
spf=fail → block and log
伪造者不在服务商的 SPF 授权列表里,所以他的邮件会被标记为 spf=fail 并丢弃。被拦截的邮件会记入日志:
[time] !! blocked suspicious mail (UID 123): SPF did not pass (sender may be forged)
claimed-from=someone@example.com [mx.example.com; spf=fail(1.2.3.4) ...]
读一下这个前提。 上面的规则建立在一个假设上:你的服务商只对从自己系统内部发出的邮件省略 Authentication-Results。 这是在 QQ 邮箱上实测的,主流服务商都成立——但这是服务商的行为,不是协议的保证。如果你用的服务商对外部邮件也省略这个头,那么「没有头 → 放行」就变成了「跳过检查」,白名单就成了你唯一的防线。
如果你想让这个假设无关紧要,就设置 secret。 有了口令,不带口令的邮件一律丢弃,不管头里写了什么。这就是「实测这家服务商行为正确」和「没有口令就跑不起来」的区别。对于一个让邮件在你机器上运行命令的程序来说,后一种性质值得那点小麻烦。
权限自愈
DSH 在创建会话时读取权限预设,改配置永远不影响已有会话。因为这个桥接复用同一个会话 id 来保持上下文,「改了权限然后重启」这套流程看起来完全没效果。
两道防线:
1. 权限指纹。 启动时,会把 DSH 权限配置文件的哈希和上一次的比对。如果变了,就清掉会话 id,这样下一封邮件会新建会话,用上新的预设。
2. 拒绝检测。 如果 DSH 事件流里某个工具结果带有操作系统层面的访问拒绝(ERROR_ACCESS_DENIED、EACCES……),桥接会丢弃会话,在新会话里重试任务一次,并在回复里说明——因为悄悄丢掉上下文,比拒绝本身更糟。
它只重试一次,绝不循环。如果新会话也被拒绝,那问题就在配置,回复会直接这么说。检测刻意要求同时出现拒绝措辞和错误码,因为误判会无缘无故清空你的对话。
测试
npm test # all suites
node tests/run-all.js permission # filter by filename
node tests/run-all.js --list # list without running
不需要邮箱,也不需要装 DSH。 测试套件自带 mock DSH(tests/helpers/mock-dsh.js),能复现成功、权限拒绝、未知会话、超时和空输出,还带假的 IMAP/SMTP 层。凡是碰配置或会话状态的测试,都在临时目录里跑,绝不碰你的真实文件。
static-audit.test.js 还会扫描源码本身,找单元测试看不见的那类错误:无法解析的相对 require、值为 undefined 的导出、残留的 debugger/TODO、src/ 里散落的 console.log,以及最重要的——认证代码或整个配置可能流进日志行的任何地方。
发布前的隐私扫描
如果你 fork 了这份代码,填了自己的信息,推送前先检查:
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/privacy-scan.ps1
它会遍历历史里的每一个提交,找残留的个人数据——邮箱地址、认证码、本地路径、用户名——还会打印提交作者信息,身份往往就是从这儿悄悄泄露的。只要发现东西就以非零码退出,所以能用来卡发布。
提交作者信息是真实的泄露渠道:git config user.name 默认取你的操作系统用户名,盖在每个提交上,在 GitHub 上永久可见。发布前特意设好它。
实机检查(可选,需要真实机器)
上面的套件全跑在替身上,抓不到「我们对真实接口的假设错了」这类问题。两个额外脚本补上这个缺口。它们不属于 npm test,因为要联网、要花真实 token。
real DSH: stdin delivery, long tasks, session resumption, stale-session recovery
node tests/integration.dsh.js # auto-skips if DSH is absent
node tests/integration.dsh.js # DSH_INTEGRATION=require to force
real mailbox: IMAP login, fetching, parsing, classification (read-only)bash
node tests/live-mail-check.js --recent
full pipeline incl. a real DSH run and a real SMTP send
node tests/live-e2e.js <UID>
live-e2e.js 不会真给任何人发邮件:它把投递地址改写成 @example.invalid(RFC 2606 保留域名,永远解析不了),这样就能跑通「撰写并发送」这条路径,又不会真的投递出去。注意,大型中继仍可能接收这样的邮件,之后再退信 —— 退信会回到你这里,不会到别人手上。
限制
- 一次只跑一个任务。 跑到超时的任务会把队列堵住。
- 邮件在处理前就被标记为已读。 失败的任务不会自动重试 —— 你会收到一封错误回复,再发一次就行。
- pollSeconds 下限是 5 秒。 轮询太频繁会被限流。
- 正文过长会被截断,上限是 maxTaskChars(默认 8000)。任务通过 stdin 投递,所以这里不受操作系统命令行长度限制 —— 这是刻意设的上限。
- 会话权限在创建时就固定了。 见上文 Permission self-healing。
经验教训
做这个项目的过程中,暴露出几种很容易踩错、又很难察觉的失败模式。它们都写进了 docs/LESSONS.md,包括:
- 邮件客户端的自动签名悄悄破坏了命令识别;
- 权限指纹变了,原因是程序写了自己的状态文件;
- 一条错误信息本身没错,但负责匹配它的解析器认不出来。
许可证
MIT。按原样提供;你让它做什么,责任在你。
部署之前先想清楚 —— 这个程序能让一封邮件在电脑上执行命令。所以:
1. 把 allowFrom 收紧到只有你自己的地址;
2. 像保管密码一样保管邮箱授权码;
3. 给它多少权限,要三思。权限越宽,出错时后果越严重。
中文说明
用一封邮件,遥控一台没有公网 IP 的电脑上的 DSH 干活。
不需要公网 IP、不需要端口转发、不需要内网穿透、不需要云函数。
原理
手机发一封邮件到自己的邮箱 → 家里的电脑通过 IMAP 主动轮询收件箱 → 把正文清洗干净后交给 DSH 执行 → 把结果通过 SMTP 回信给手机。
全程只用到主动连出去的连接,所以路由器后面的电脑也能用。
快速开始 bash
git clone https://github.com/QIKO0721/dsh-mail-bridge.git
cd dsh-mail-bridge
npm install
npm run init # 生成 config.json,并告诉你该填什么
→ 填 email、authCode、allowFrom
npm run doctor # 环境自检(不连邮箱、不跑任务)
npm start
authCode 是邮箱授权码,不是登录密码。以 QQ 邮箱为例:设置 → 账户 → 开启「IMAP/SMTP 服务」→ 按提示发一条短信 → 拿到 16 位授权码。
特殊指令
正文只写下面之一:
| 正文 | 作用 |
| 帮助 / help | 回一封用法说明 |
| 新会话 / 新对话 / new / reset | 清空记忆,下一篇邮件当全新对话 |
注意:清空是下一篇才生效 —— 发指令的那封信是直接回你的,没有经过 DSH。
安全提醒
- 授权码等于密码,泄露了别人就能收发你的邮件。别提交、别截图。
- allowFrom 必须收紧到只有你自己,那是第一道闸门。
- 光看发件人地址不够 —— 地址可以伪造,所以程序还会校验 SPF/DKIM(原理见上面英文部分)。
- 它能执行命令:读写文件、跑程序。这是设计如此,也是风险所在。
权限自愈
DSH 的权限预设只在创建会话时读一次,改完配置对已有会话无效。
而本程序为了记住上下文会复用同一个会话 —— 于是「改完权限 → 重启 → 毫无变化」。
程序有两道防线:启动时比对权限配置指纹(变了就换会话),以及干活时发现操作系统级拒绝就自动换新会话重跑一次(只重试一次,会如实告知)。
测试bash
npm test
不需要真邮箱,也不需要装 DSH —— 测试自带假的 DSH 和假的收发信层,配置、会话状态都写在临时目录里,不碰你的真实文件。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
QIKO0721/dsh-mail-bridge
用手机发邮件,让家里电脑的 AI 替你干活并回信
相关插件 · 平台集成
智能体工程课walkinglabs/learn-harness-engineering
一门项目制课程,教你搭建环境、管理状态、做验证、加控制机制,让 AI 编程智能体稳定干活。
★ 21,155已汉化DSH
71.7
视频翻译配音liuzhao1225/YouDub-webui
开源视频本地化工作台:导入视频,识别、翻译并逐句克隆原声,生成字幕和配音成片。
★ 5,792已汉化Cordis
71.4
失败经验库Ikalus1988/MisakaNet
📚 零依赖、由 git 支撑的微课程库,让 AI 智能体异步分享并检索经过验证的调试经验。| https://misakanet.org
★ 550已汉化DSH
71.2
桌面智能体工作区freestylefly/wesight
WeSight 是开源桌面控制台,面向本地 AI 智能体。它帮你安装或复用 Claude…
★ 933已汉化MCP
71
IM 机器人接入xmanrui/dsh-im
让 DeepSeek Harness 触手可及 Connecting DeepSeek Harness
★ 3,110已汉化DeepSeek 客户端
70.9
中国平台研究引擎Jesseovo/last30days-skill-cn
给 AI Agent 的中文互联网研究 Skill:检索 8 大平台最近 30…
★ 2,031已汉化DSH
70.7
这是你的仓库?认领条目