桥接协议说明

August 20, 2026 · View on GitHub

bin/mail-bridge.mjs 是一个零依赖的独立进程。宿主半通过

node bin/mail-bridge.mjs --payload <base64(JSON)>

调用它,进程在 stdout 输出

<<<DSH_MAIL_JSON>>>{ ...JSON... }<<<DSH_MAIL_END>>>

宿主半用 lastIndexOf 定位这对标记后 JSON.parse 取结果,因此子进程的其他日志不会干扰解析。

三种输入方式等价:

方式用法
--payload--payload <base64 的 JSON>(宿主半使用,避免命令行转义与中文编码问题)
--file--file ./request.json
stdinecho '<json>' | node bin/mail-bridge.mjs

config 对象

{
  "email": "you@163.com",        // 必填
  "password": "客户端授权码",       // 必填(网易/QQ 必须用授权码)
  "imapHost": "imap.163.com",    // 缺省按域名推断
  "imapPort": 993,
  "imapTls": true,
  "smtpHost": "smtp.163.com",    // 缺省按域名推断
  "smtpPort": 465,
  "smtpSecure": "tls",           // "tls"(465)或 "starttls"(587/25)
  "displayName": "张三",          // 发件人显示名,可选
  "allowInsecureTls": false,      // 自建服务器证书不受信时才置 true
  "timeoutMs": 30000
}

请求与响应

selftest

// → { "op": "selftest" }
{ "ok": true, "node": "v24.18.0", "platform": "win32", "presets": ["163.com", "..."] }

guess

// → { "op": "guess", "email": "x@126.com" }
{ "ok": true, "preset": { "domain": "126.com", "imapHost": "imap.126.com", "smtpHost": "smtp.126.com", "note": "..." } }

test

同时验证 IMAP 登录与 SMTP 认证。

// → { "op": "test", "config": { ... } }
{
  "ok": true,
  "imap": { "capabilities": ["IMAP4rev1", "..."], "mailboxes": ["INBOX", "已发送"], "total": 128, "unseen": 3 },
  "smtp": { "ok": true, "auth": "LOGIN" },
  "config": { "email": "...", "imapHost": "...", "smtpHost": "..." }   // 不含密码
}

mailboxes

{ "ok": true, "mailboxes": [{ "name": "INBOX", "flags": ["\\HasNoChildren"], "special": "inbox" }] }

special 取值:inbox / sent / drafts / junk / trash / ""

list

search 时按序号倒序分页;带 search 时走 UID SEARCH,中文关键词自动使用 CHARSET UTF-8 字面量。

// → { "op": "list", "config": {...}, "mailbox": "INBOX", "limit": 25, "offset": 0, "search": "发票" }
{
  "ok": true,
  "mailbox": "INBOX",
  "total": 128,
  "unseen": 3,
  "offset": 0,
  "limit": 25,
  "searched": true,
  "messages": [
    {
      "seq": 128, "uid": 4021, "size": 20481,
      "flags": ["\\Seen"], "seen": true,
      "date": "19-Aug-2026 10:22:31 +0800",
      "subject": "八月发票", "from": "财务 <fin@example.com>",
      "to": "you@163.com", "cc": "", "messageId": "<...>"
    }
  ]
}

message

完整解析一封邮件。markSeen: true 会用 SELECT 而非 EXAMINE 并置 \Seen

{
  "ok": true, "mailbox": "INBOX", "uid": 4021, "size": 20481,
  "headers": { "subject": "八月发票", "from": "...", "to": "...", "cc": "", "date": "...", "messageId": "<...>" },
  "text": "解码后的纯文本正文",
  "html": "<p>原始 HTML(若有)</p>",
  "attachments": [{ "part": "1.2", "filename": "invoice.pdf", "mime": "application/pdf", "size": 10240, "inline": false }]
}

part 编号规则:整封邮件是 1,multipart 的第 n 个子部件是 <父编号>.<n>,例如 1.21.1.2download 用它定位附件。

download

// → { "op": "download", "config": {...}, "uid": 4021, "part": "1.2", "dir": "./mail-attachments" }
{ "ok": true, "path": "E:\\...\\mail-attachments\\invoice.pdf", "filename": "invoice.pdf", "mime": "application/pdf", "size": 10240 }

同名文件自动加 -1-2 后缀;文件名中的非法字符被替换为 _

send

// → {
//   "op": "send", "config": {...},
//   "to": "a@example.com, b@163.com", "cc": "c@example.com",
//   "subject": "周报", "text": "纯文本", "html": "<b>富文本</b>",
//   "attachments": [{ "path": "E:\\files\\report.pdf" }]
// }
{ "ok": true, "recipients": ["a@example.com", "b@163.com", "c@example.com"], "bytes": 20480, "detail": "250 Mail OK queued" }
  • 同时给 texthtml 时生成 multipart/alternative;有附件时再包一层 multipart/mixed
  • 只给 html 时自动生成纯文本兜底部件。
  • 中文主题与显示名按 RFC 2047 编成 =?UTF-8?B?...?=;正文统一 base64 编码。
  • 附件也可用 { "filename": "a.txt", "contentBase64": "..." } 直接传字节。

flag

// → { "op": "flag", "config": {...}, "uid": 4021, "add": ["\\Seen"], "remove": ["\\Flagged"], "expunge": false }
{ "ok": true, "uid": 4021, "add": ["\\Seen"], "remove": ["\\Flagged"] }

\\Deleted 并置 expunge: true 即为真正删除。

saveConfig / loadConfig / forgetConfig

账户配置存于 <dir>/account.json,默认 dir 为工作区下 .dsh-mail

// saveConfig → { "ok": true, "path": "...\\account.json", "account": { /* 不含密码 */ } }
// loadConfig → { "ok": true, "path": "...", "savedAt": "2026-08-19T08:14:08.426Z", "account": { /* 含 password */ } }
// forgetConfig → { "ok": true, "path": "...", "removed": true }

文件里密码字段名为 secret,值是 base64 编码——这只是混淆,不是加密

错误

任何失败都返回 ok: false,且进程退出码为 1:

{ "ok": false, "op": "list", "error": "IMAP LOGIN 失败: Login fail. Please enter authorization code" }

常见错误对照:

错误信息原因
Login fail. Please enter authorization code网易邮箱用了登录密码,需改用客户端授权码
Unsafe Login未发送 ID 命令(引擎已自动发送;若仍出现请检查是否开启 IMAP 服务)
connect ETIMEDOUT端口被防火墙拦截,或服务商未开启 IMAP
self signed certificate自建服务器证书不受信,可置 allowInsecureTls: true
SMTP 命令失败 (535)SMTP 认证失败,检查授权码与加密方式(465→tls,587→starttls)

协议实现范围

IMAP(RFC 3501):IDLOGINCAPABILITYLISTSELECT/EXAMINEUID FETCHUID SEARCHUID STOREEXPUNGELOGOUT。响应解析支持原子、引号串、{n} 字面量与嵌套括号列表。

SMTP(RFC 5321):EHLOSTARTTLSAUTH LOGINAUTH PLAINMAIL FROMRCPT TODATAQUIT,支持多行响应码。

MIME:RFC 2045(Content-Type / Content-Transfer-Encoding / base64 / quoted-printable)、RFC 2047(编码字)、RFC 2231(filename* 与分段参数)、GBK/GB2312 等非 UTF-8 字符集解码。