6.6 KiB
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。它会:
- 若
ui/dist尚未构建,自动npm install+npm run build(首次约一两分钟); - 起一个只监听
127.0.0.1:8788的本地服务(界面与邮件协议之间的桥); - 自动打开浏览器指向
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 覆盖界面所用的那条链路:发送 → 到达 → 旗标 → 未读 → 移动(归档/移回)→ 删除 → 清理痕迹。
已知限制(都是真的,没藏)
- 搜索是「服务端取回摘要 + 本地过滤」,窗口上限 500 封,不含正文全文。
原因:服务端
SEARCH没实现BODY/OR,且命令行按Encoding.ASCII解码 → 中文检索词到服务端 会变成??(详见服务端缺陷记录)。客户端绕过了它,所以中文搜索可用。 - 不渲染 HTML 邮件正文,只渲染解析出的纯文本(避免 XSS)。只有 HTML 的邮件会退化成去标签文本。
- 附件只支持「添加并随信发出」与「下载」,不做内联图片渲染。
- 密码以明文存在
data\account.json(本机、便携)。要更稳妥可以用 Windows DPAPI 包一层。 - 未实现:IDLE 实时推送(目前 30 秒轮询文件夹计数)、会话视图、离线缓存、富文本撰写、多账号。
- 服务端目前没有
SPECIAL-USE,文件夹靠名字映射。 web/(旧手写界面)已退役但保留作兜底;日常运行的是ui/dist。
排障
- 界面显示"正在连接邮件服务器…"不动:看黑窗口里的报错,多半是服务器 993 连不上或密码变了。
- 发送失败:报错原样显示服务端返回(如
550 Relay denied)。家用 DNS 偶发瞬时解析失败 (EAI_AGAIN)已加一次自动重试。 - 改了界面没生效:
npm --prefix ui run build重新构建(ui/dist是静态产物,改了源码不会自动更新)。 - 端口被占:设环境变量
WPYWMAIL_PORT换端口。