桥接协议说明
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 |
| stdin | echo '<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.2、1.1.2。download 用它定位附件。
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" }
- 同时给
text与html时生成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):ID、LOGIN、CAPABILITY、LIST、SELECT/EXAMINE、UID FETCH、UID SEARCH、UID STORE、EXPUNGE、LOGOUT。响应解析支持原子、引号串、{n} 字面量与嵌套括号列表。
SMTP(RFC 5321):EHLO、STARTTLS、AUTH LOGIN、AUTH PLAIN、MAIL FROM、RCPT TO、DATA、QUIT,支持多行响应码。
MIME:RFC 2045(Content-Type / Content-Transfer-Encoding / base64 / quoted-printable)、RFC 2047(编码字)、RFC 2231(filename* 与分段参数)、GBK/GB2312 等非 UTF-8 字符集解码。