WpywMail 客户端

给自建邮件服务器 WpywMail(mail.example.com)写的桌面客户端。 收信走 IMAP4rev1(993 隐式 TLS),发信走 SMTP 提交(587 STARTTLS + AUTH)。

界面用 shadcn/ui + Tailwind CSS v4 + Vite(React 19) —— 结构与视觉以 shadcn 官方的 Mail 示例为基线,控件不自己发明。后端是零依赖的 Node 服务,负责讲 IMAP/SMTP 与 MIME。

怎么启动

双击 start.cmd。它会:

  1. 若 ui/dist 尚未构建,自动 npm install + npm run build(首次约一两分钟);
  2. 起一个只监听 127.0.0.1:8788 的本地服务(界面与邮件协议之间的桥);
  3. 自动打开浏览器指向 http://127.0.0.1:8788/。

关闭那个黑窗口就等于退出客户端。需要 Node.js 20+(当前机器 v24)。

改界面时用热更新:npm --prefix ui run dev(已配好把 /api 代理到 8788)。

首次启动若 data\account.json 里没有账号,界面会要求登录;登录成功后凭据写回该文件(只在本机)。

现在能做什么

能力 说明
收信 6 个文件夹(收件箱/草稿/已发送/归档/垃圾邮件/废纸篓),带未读数
邮件列表 头像、发件人、主题、时间、大小、未读圆点、旗标、已回复/草稿标记
阅读区 中文主题与正文正确解码、附件下载、发件人与收件人信息、完整时间
撰写 / 回复 / 转发 弹窗式;中文头部自动 RFC 2047 编码;多附件;回复带 In-Reply-To/References
快速回复 阅读区底部直接写,Ctrl+Enter 发送
草稿 「存草稿」写入 Drafts
搜索 主题 / 发件人 / 收件人 / UID,支持中文(见「已知限制」)
管理动作 已读未读、旗标、归档、删除(默认进废纸篓)、切换文件夹
主题 深色/浅色双主题,切换后记忆(默认深色)

键盘

键 动作
J / K(或 ↑↓) 上下选邮件
Enter 打开
C 撰写
R / F 回复 / 转发
S 加/取消旗标
U 已读/未读切换
E 归档
# 删除
/ 聚焦搜索框
Esc 关闭弹窗 / 取消焦点

架构

start.cmd
  └─ node server/index.js            本地服务(仅 127.0.0.1:8788):托管界面 + JSON API
       ├─ server/imap.js             IMAP4rev1 客户端(自己实现,含字面量感知解析器)
       ├─ server/smtp.js             SMTP 提交客户端(STARTTLS + AUTH PLAIN,DATA 按字节写出)
       ├─ server/mime.js             MIME 编解码(RFC 5322/2047/2231/2045)
       ├─ server/account.js          账户配置读写(data/account.json)
       ├─ ui/                        界面源码(shadcn/ui + Tailwind + Vite)→ 构建到 ui/dist
       └─ web/                       旧版纯手写界面(兜底;ui/dist 不存在时才会被发出去)

为什么后端零依赖:IMAP/SMTP/MIME 是这块的硬骨头,自己实现能对症下药 —— 实测用它挖出了服务端 三个真实缺陷(见 E:\deepseek\artifacts\wpywmail-server-findings.md),并且不引入供应链风险。 为什么界面用现成组件库:控件观感不必自己发明。shadcn 是「把源码复制进项目」的组件(不是黑盒依赖), 随手可改,同时白拿 Radix 的可访问性底座与 Tailwind 的迭代速度。

设计系统

结构实例化 shadcn/ui 官方 Mail 示例(左:账号 + 文件夹导航;中:邮件列表;右:阅读与操作)。 配色、圆角、阴影、暗色全部用 shadcn 默认(new-york / neutral)主题 token,原样写在 ui/src/styles.css(oklch() 变量 + .dark 覆盖),所以 light/dark 都是"官方观感"。 换主题只改那一个文件的变量块。

字体走系统栈(Segoe UI / 微软雅黑 / PingFang)—— 邮件客户端要的是"读起来像系统原生",不是品牌表达。

验收记录

套件 结果 报告
MIME 单元测试 node tools/mime-test.js 25/25 E:\deepseek\artifacts\client-mime-test.txt
协议闭环自测 node tools/smoke.js 20/20 E:\deepseek\artifacts\client-smoke.txt
后端 API 链路 node tools/api-check.js 10/10 E:\deepseek\artifacts\client-api-check.txt
界面运行时(新 UI)node tools/ui-check-v2.js 8/8 E:\deepseek\artifacts\client-ui-check-v2.txt

smoke.js 是真正的闭环:用本项目的代码发一封中文信 → 服务器投递 → 再用本项目的 IMAP 代码 把它从收件箱取回并解析,断言主题与正文(含全角标点)往返无损。

ui-check-v2.js 用 Edge 无头 + CDP(Node 自带 WebSocket,零依赖) 真开页面:收集 JS 异常与 控制台报错、断言文件夹/列表/阅读区/撰写弹窗都渲染、验证主题切换真的改变背景色、检查无横向溢出。

api-check.js 覆盖界面所用的那条链路:发送 → 到达 → 旗标 → 未读 → 移动(归档/移回)→ 删除 → 清理痕迹。

已知限制(都是真的,没藏)

  1. 搜索是「服务端取回摘要 + 本地过滤」,窗口上限 500 封,不含正文全文。 原因:服务端 SEARCH 没实现 BODY/OR,且命令行按 Encoding.ASCII 解码 → 中文检索词到服务端 会变成 ??(详见服务端缺陷记录)。客户端绕过了它,所以中文搜索可用。
  2. 不渲染 HTML 邮件正文,只渲染解析出的纯文本(避免 XSS)。只有 HTML 的邮件会退化成去标签文本。
  3. 附件只支持「添加并随信发出」与「下载」,不做内联图片渲染。
  4. 密码以明文存在 data\account.json(本机、便携)。要更稳妥可以用 Windows DPAPI 包一层。
  5. 未实现:IDLE 实时推送(目前 30 秒轮询文件夹计数)、会话视图、离线缓存、富文本撰写、多账号。
  6. 服务端目前没有 SPECIAL-USE,文件夹靠名字映射。
  7. web/(旧手写界面)已退役但保留作兜底;日常运行的是 ui/dist。

排障

  • 界面显示"正在连接邮件服务器…"不动:看黑窗口里的报错,多半是服务器 993 连不上或密码变了。
  • 发送失败:报错原样显示服务端返回(如 550 Relay denied)。家用 DNS 偶发瞬时解析失败 (EAI_AGAIN)已加一次自动重试。
  • 改了界面没生效:npm --prefix ui run build 重新构建(ui/dist 是静态产物,改了源码不会自动更新)。
  • 端口被占:设环境变量 WPYWMAIL_PORT 换端口。
S
Description
WpywMail 桌面客户端:Node 零依赖本地服务 + React 19 / shadcn-ui 界面,支持收发信、注册、找回密码、会话与账号管理
Readme
167 KiB
Languages
JavaScript 63.5%
TypeScript 34%
CSS 1.9%
Batchfile 0.4%
HTML 0.2%