Back to home@aizzaua

dsh-mail-connector

A DeepSeek Harness plugin that gives your AI a mailbox — zero-dependency IMAP/SMTP engine for reading, searching, attachments, and sending.DeepSeek Harness 邮箱插件 · 零依赖 IMAP/SMTP 引擎,让 AI 读信、搜索、下载附件、发信。

Stars
0
Language
JavaScript
Created
Aug 20, 2026
Updated
Aug 20, 2026
GitHub repo

Introduction

简体中文 · English

dsh-mail-connector · Mailbox Connector for DeepSeek Harness

Connect any mailbox over IMAP/SMTP: read, search, send, and download attachments. Works with Netease mail (163, 126, yeah.net) and any other standard IMAP/SMTP provider.

Adds a Mail button to the composer toolbar in the DeepSeek Harness Web UI — click it, fill in your address and IMAP/SMTP details, and you're connected. It also registers a mail tool with the model, so the AI can read, search, download attachments, and send mail on your behalf.

plugin type deps node license


✨ Features

CapabilityDetails
📥 ReceiveList messages in any folder with sender / subject / date / unread state
📖 ReadFull MIME parsing: multipart, base64, quoted-printable, GBK/GB2312 bodies, HTML-to-text
🔍 SearchBy subject / sender / body, including non-ASCII keywords (IMAP CHARSET UTF-8 literals)
📎 AttachmentsList attachments and download them to a local directory in one click
✉️ SendPlain text / HTML / attachments, To and Cc, non-ASCII subjects auto-encoded per RFC 2047
🗂 FoldersAuto-detects Inbox / Sent / Drafts / Junk / Trash
🤖 AI toolThe model can call the mail tool to do everything above
🔐 CredentialsAccount config stays in your local workspace — no third-party service involved

📦 Deployment Guide

Zero dependencies, no build step, no npm install required. Deployment is three moves: clone → configure the path → activate inside DSH.

Prerequisites

RequirementNeededHow to check
Node.js≥ 18 (developed on v24)node --version
DeepSeek Harness0.1.0-rc.7 or newer, with Cordis dynamic plugins availableDSH Web UI reachable
NetworkOutbound access to your mail server on 993 / 465 (or 587)See Troubleshooting
MailboxIMAP enabled, with a client authorization code / app password issuedSee Netease setup

Step 1 — Clone

git clone https://github.com/aizzaua/dsh-mail-connector.git
cd dsh-mail-connector

Any directory works, but pin the path down — the host half needs an absolute path to launch the bridge process. Prefer a path without spaces or non-ASCII characters (this project handles them fine, but some third-party sandboxes don't).

Step 2 — Write your local path

src/host.js has a PROJECT_ROOT constant near the top pointing at the repo root. Run this once to point it at your own directory:

node scripts/configure-paths.mjs

✓ 已更新 src/host.js means it worked. Other usages:

node scripts/configure-paths.mjs --check          # inspect current config, write nothing
node scripts/configure-paths.mjs /opt/dsh-mail    # target an explicit directory

You can also leave the code untouched and set the DSH_MAIL_ROOT environment variable to the repo root before starting DSH. With neither set, it falls back to the DSH process working directory. Resolution order: PROJECT_ROOT constant > DSH_MAIL_ROOT env var > process working directory.

Step 3 — Pre-flight check

node scripts/check.mjs

It verifies, in order: both plugin halves parse as function bodies; neither contains import/export or other syntax cordis_define rejects; the bridge engine's selftest passes; PROJECT_ROOT points at this repo; and any credential file in the workspace is excluded by .gitignore. An all-green run looks like this:

1) 插件两半语法
  ✓ src/host.js 可作为函数体解析(12474 字符)
  ✓ src/client.js 无 import/export/类型注解
2) 桥接引擎 selftest
  ✓ 引擎可用(Node v24.18.0 / win32 / 17 个域名预设)
3) PROJECT_ROOT 配置
  ✓ 指向当前仓库:/opt/dsh-mail-connector
4) 凭据文件
  ✓ 工作区内没有本地凭据文件

✓ 全部检查通过,可以发布。

Script output is currently Chinese-only. The check names map to the five bullet points above, in order.

Step 4 — Activate in DeepSeek Harness

In a Cordis-mode session, pass the complete contents of the two files as code.host and code.client to cordis_define, then activate with cordis_run:

ParameterValue
namemail-connector
code.hostentire contents of src/host.js
code.cliententire contents of src/client.js

The easiest route is to let the model in DSH do it:

"Read src/host.js and src/client.js, define a plugin named mail-connector with cordis_define, then activate it with cordis_run."

Signs it worked:

  1. The host log prints mail-connector host ready, root = … , transport = subprocess;
  2. A Mail button appears at the left of the composer (the first activation may ask you to approve it in the UI);
  3. mail shows up in the model's tool list.

⚠️ A dynamic Cordis plugin definition lives only inside the current DSH process, so you must re-run cordis_define + cordis_run after restarting DSH. The sources in this repo are the definition, ready to resubmit; your account config lives on disk and survives restarts.

Step 5 — Connect your mailbox

  1. Click the Mail button at the left of the composer;
  2. Enter your email address — IMAP/SMTP servers are inferred automatically;
  3. In the password field, enter your client authorization code (Netease and QQ mail require this, not your login password);
  4. Hit "测试连接" (Test connection) to verify, then "保存并连接" (Save and connect).

The dot on the button turns green once saved; switch to the inbox tab to see your mail. Credentials are written to .dsh-mail/account.json (excluded by .gitignore).

To pre-seed an account from a script, see the field layout in .dsh-mail.example.json.

Deploying to another machine or a server

Same flow, with two caveats:

  • Path: re-run node scripts/configure-paths.mjs on the new machine, or set DSH_MAIL_ROOT. Never carry the old machine's absolute path over.
  • Credentials: .dsh-mail/ is not in the repo, so you'll re-enter the account details in the UI once.
# Full server deployment
git clone https://github.com/aizzaua/dsh-mail-connector.git /opt/dsh-mail-connector
cd /opt/dsh-mail-connector
node scripts/configure-paths.mjs
node scripts/check.mjs
# then cordis_define + cordis_run in a DSH session

Upgrading

git pull
node scripts/configure-paths.mjs   # a pull overwrites PROJECT_ROOT, so re-run it
node scripts/check.mjs
# re-run cordis_define + cordis_run in DSH to load the new code

📮 Netease Mail Setup

163 / 126 / yeah.net require enabling IMAP and obtaining an authorization code first:

  1. Log into the web mail → SettingsPOP3/SMTP/IMAP;
  2. Enable IMAP/SMTP service;
  3. After SMS verification, the site shows a client authorization code;
  4. Paste that code into the plugin's "密码 / 客户端授权码" (password / authorization code) field.

Built-in presets:

DomainIMAPSMTPEncryption
163.comimap.163.com:993smtp.163.com:465SSL/TLS
126.comimap.126.com:993smtp.126.com:465SSL/TLS
yeah.netimap.yeah.net:993smtp.yeah.net:465SSL/TLS
188.com / vip.163.comimap.188.com / imap.vip.163.com:993matching smtp:465SSL/TLS
qq.com / foxmail.comimap.qq.com:993smtp.qq.com:465SSL/TLS
exmail.qq.comimap.exmail.qq.com:993smtp.exmail.qq.com:465SSL/TLS
gmail.comimap.gmail.com:993smtp.gmail.com:465SSL/TLS
outlook.com / hotmail.comoutlook.office365.com:993smtp.office365.com:587STARTTLS
sina / sohu / aliyun / yahoo / icloudsee PRESETS in bin/mail-bridge.mjs

Unlisted domains are guessed as imap.<domain> / smtp.<domain>, and can always be entered manually.

🤖 The mail Model Tool

Once activated, the model can call:

{ "op": "list",     "mailbox": "INBOX", "limit": 20 }   // list messages
{ "op": "search",   "query": "invoice" }                  // search
{ "op": "message",  "uid": 12345 }                        // read a message body
{ "op": "download", "uid": 12345, "part": "1.2" }         // download an attachment
{ "op": "send", "to": "a@example.com", "subject": "Hi", "text": "body" }
{ "op": "mailboxes" }                                     // folder list
{ "op": "test" }                                          // test the connection

Example prompts:

"Show me the 10 most recent emails in my inbox" "Find messages mentioning invoices and download their attachments" "Send zhang@example.com an email with the subject 'Weekly Report'"

🖥 Standalone CLI Use

The bridge engine is a runnable CLI in its own right, with no dependency on Harness:

# Simplest: JSON on stdin
echo '{"op":"test","config":{"email":"you@163.com","password":"AUTH_CODE"}}' | node bin/mail-bridge.mjs

# Or a request file (more reliable where pipes are restricted)
node bin/mail-bridge.mjs --file ./request.json

# Or a base64 argument (this is what the host half's fallback path uses)
node bin/mail-bridge.mjs --payload $(printf '%s' '{"op":"list","config":{"email":"you@163.com","password":"AUTH_CODE"},"limit":10}' | base64 -w0)

Results are wrapped in <<<DSH_MAIL_JSON>>>{...}<<<DSH_MAIL_END>>> markers so programs can extract them reliably.

Supported ops

opParametersPurpose
selftestEnvironment self-check
guessemailInfer server settings
testconfigVerify both IMAP and SMTP
mailboxesconfigFolder list
listconfig, mailbox?, limit?, offset?, search?List / search
messageconfig, uid, mailbox?, markSeen?Read a full message
downloadconfig, uid, part, dir?Download an attachment
sendconfig, to, cc?, bcc?, subject?, text?, html?, attachments?Send mail
flagconfig, uid, add?, remove?, expunge?Change flags / delete
saveConfig / loadConfig / forgetConfigconfig?, dir?Persist account config

Full request/response examples live in docs/protocol.md.

🏗 Architecture

Browser (client half · src/client.js)
  ├─ conversation.input.left  →  Mail button
  └─ shell.overlay            →  config / inbox / reader / composer panels
              │  host.call('mail', {...})   ← package-private JSON RPC
              ▼
Host (host half · src/host.js)
  ├─ harness.handle('mail')     →  for the browser
  └─ harness.registerTool(mail) →  for the model
              │  ctx.subprocess.spawn(['node', 'bin/mail-bridge.mjs'])  ← request via stdin
              ▼
Bridge engine (bin/mail-bridge.mjs · zero deps)
  ├─ ImapClient  ── node:tls     ── IMAP4rev1 (LOGIN/ID/LIST/EXAMINE/UID FETCH/UID SEARCH/STORE)
  ├─ SmtpClient  ── node:tls/net ── SMTP (EHLO/STARTTLS/AUTH LOGIN|PLAIN/DATA)
  └─ MIME parsing and assembly (RFC 822 / 2045 / 2047 / 2231)

Why a separate process? The browser half cannot open TCP sockets, and the host half's restricted runtime has no access to node:tls. Putting the protocol implementation in a child process launched by ctx.subprocess gets full Node capability while ensuring every side effect disappears when the plugin stops.

Why ctx.subprocess rather than ctx.shell? ctx.shell is forced through the command sandbox layer, which outright refuses to execute on hosts where no sandbox backend is usable (see Troubleshooting). ctx.subprocess is the lower-level process seam; passing the request over stdin also sidesteps argument-length limits and non-ASCII escaping issues.

📁 Repository Layout

.
├── bin/
│   └── mail-bridge.mjs         # zero-dependency IMAP/SMTP engine (runs standalone)
├── src/
│   ├── host.js                 # Cordis host half: RPC + the mail model tool
│   └── client.js               # Cordis client half: Mail button + overlay panels
├── scripts/
│   ├── configure-paths.mjs     # writes PROJECT_ROOT to your local path
│   └── check.mjs               # pre-flight check (syntax / engine / paths / credentials)
├── docs/
│   └── protocol.md             # bridge protocol with request/response examples
├── .dsh-mail.example.json      # account config field example (no real secrets)
├── .gitignore                  # excludes .dsh-mail/ and other sensitive paths
├── .gitattributes
├── package.json
├── LICENSE
├── README.md                   # English
└── README.zh-CN.md             # Chinese

Generated at runtime, never committed:
├── .dsh-mail/account.json      # your address and authorization code
└── mail-attachments/           # downloaded attachments

🔒 Security Notes

  • Credentials live only in .dsh-mail/account.json on your machine, with the password base64-encoded (obfuscation, not encryption). Keep that directory out of the repo — it is already in .gitignore.
  • Every connection goes straight to the mail server you configured. No intermediary service.
  • TLS certificates are verified by default; allowInsecureTls is only for self-hosted servers with untrusted certificates.
  • Always prefer a client authorization code / app-specific password over your primary password.
  • The mail tool can read all of your mail and send as you — only activate this plugin in sessions you trust.

🛠 Troubleshooting

Bridge fails to start: no sandbox backend is usable on this host

The full error looks like:

sandbox mode "workspace-write" is requested but no sandbox backend is usable on this host;
refusing to run the command unconfined. ...
Runner failure: windows-acl-run: Windows ACL temp root must be outside the workspace

Cause: when the host half runs commands through ctx.shell, they are forced through the command sandbox layer. If no sandbox backend is usable on the host (e.g. the Windows ACL runner fails because its temp root falls inside the workspace), the sandbox refuses to execute rather than running unconfined.

How this project handles it: src/host.js prefers ctx.subprocess to spawn the child directly — a lower-level process seam with no command interdiction — and passes the JSON request over stdin instead of argv (which also avoids long-argument and escaping problems). ctx.shell is kept only as a fallback when subprocess is unavailable.

If you extend the host half and hit the same error, restructure your transport the same way rather than loosening sandbox settings.

未找到 node 可执行文件 (node executable not found) or 桥接进程没有返回结果 (bridge returned no result)

Almost always a path mismatch. Check in order:

node scripts/configure-paths.mjs --check   # does PROJECT_ROOT point at this repo?
node scripts/check.mjs                     # does the engine run standalone?

If both pass but DSH still fails, have the model call the host's paths handler (it returns projectRoot / bridgePath / transport) and confirm the paths the host process sees actually exist.

spawnSync … EPERM (in the check script)

Restricted environments (the DSH sandbox, some CI containers) do not let processes open named pipes. scripts/check.mjs works around this by capturing child output through a temp file. If you script against the bridge engine yourself, pass the request with --file and redirect stdout to a file rather than capturing through a pipe.

Login fail. Please enter authorization code

A login password was used for Netease / QQ mail. Switch to the client authorization code.

connect ETIMEDOUT

IMAP is not enabled on the provider side, or a local network/firewall is blocking port 993 / 465.

SMTP 命令失败 (535) (SMTP command failed)

SMTP authentication failed. Check the authorization code, and make sure encryption matches the port: 465 uses SSL/TLS, 587 uses STARTTLS.

The plugin disappears after restarting DSH

Dynamic Cordis plugin definitions exist only within the current DSH process; re-run cordis_define + cordis_run after a restart. src/host.js and src/client.js in this repo are exactly the sources to resubmit, and your account config persists on disk.

More error mappings are in docs/protocol.md.

🧩 Compatibility

  • Node.js ≥ 18 (developed on v24)
  • Windows / macOS / Linux
  • DeepSeek Harness 0.1.0-rc.7 or newer (Cordis dynamic plugins)

🤝 Contributing

Issues and PRs welcome. Please run node scripts/check.mjs before submitting, and confirm git status --short shows no .dsh-mail/.

📄 License

MIT