最近我折腾了一个很有意思的开源项目:Pelton
它是一款比较新的开源桌面邮件客户端,主打本地数据、隐私、离线搜索和轻量化。项目使用 Go + Wails 构建,前端采用 Svelte,采用 GPL-3.0 协议开源。官方也明确欢迎社区贡献,包括翻译、Bug 修复和 UI 改进。
项目地址:
Pelton 官方网站 pelton.app
Pelton GitHub 源码仓库 github.com/TRC-Loop/Pelton?utm_source=chatgpt.com
一开始我的需求其实非常简单:
我只是想让 Pelton 支持简体中文。
没想到最后一路折腾,从 i18n 翻译一路做到 HTML 邮件 Dark Mode、颜色分析算法、QQ 邮箱 IMAP、已发送邮件归档,甚至搭建了 IMAP 仿真服务器进行协议级测试。
所以我觉得,这个过程很值得记录下来。
一、为什么选择 Pelton?
Pelton 吸引我的地方其实很简单。
它不是传统的 Electron 邮件客户端,而是使用 Go + Wails 构建,界面使用 Svelte,同时强调:
本地数据
IMAP / SMTP
离线使用
本地全文搜索
无遥测
GPL-3.0
Windows / macOS / Linux
官方也明确说明邮件数据保存在本地 SQLite 和原始邮箱服务商中,不经过 Pelton 自己的服务器。
对于我这种喜欢折腾自己的软件、服务器和开源项目的人来说,这种设计很有吸引力。
但是当时有一个比较明显的问题:
没有简体中文。
所以我决定:
自己改。
二、第一步:先把 Pelton 源码下载下来
如果只是普通用户,直接去官网下载就可以。
但是如果准备修改 Pelton,就应该直接从源码开始。
官方源码仓库:
https://github.com/TRC-Loop/Pelton
我使用 Git 克隆源码:
git clone https://github.com/TRC-Loop/Pelton.git
cd Pelton
之后先不要急着修改。
第一件事情应该是:
把原项目完整编译一次。
这样可以确认自己的环境没有问题。
如果源码本身都无法正常编译,后面出现的问题就很难判断到底是:
原项目问题
环境问题
还是自己的修改造成的问题。
Pelton 使用 Go + Wails,因此源码开发环境需要准备相应的 Go / Node.js / pnpm / Wails 等工具。
三、为什么我没有自己直接改代码?
这里其实是这次经历最有意思的地方。
我并不是程序员。
我的需求很明确:
“我要一个中文 Pelton。”
但如果让我自己从几十个源文件里找:
Inbox
Sent
Archive
Settings
Compose
Account
...
然后一个一个改,我大概率会很快放弃。
于是我选择了一个现在越来越有意思的开发方式:
让 AI Agent 帮我分析和修改源码。
这里负责实际代码分析、修改、测试和编译的是 DSH。
但我并没有直接告诉它:
“帮我把 Pelton 汉化。”
而是让它先:
分析项目结构
找到 i18n 架构
找到语言文件
判断哪些文本已经国际化
判断哪些文本仍然硬编码
确定最小修改范围
修改
编译
检查
这种方式最大的好处是:
每一步都有证据,而不是让 AI 直接“自由发挥”。
四、第一阶段:增加简体中文语言
第一阶段其实相对顺利。
DSH 首先分析了 Pelton 当前的语言结构,然后增加:
zh-CN
对应的 Simplified Chinese locale。
最终形成类似:
frontend/src/lib/locales/zh-CN.ts
这样的语言文件。
这一阶段最重要的原则是:
尽可能不修改原来的 i18n 架构。
也就是说,不重新设计语言系统,而是按照项目原本的方式增加中文。
完成以后进行:
pnpm check
以及:
pnpm build
确认没有破坏原有功能。
最终形成:
feat(i18n): add Simplified Chinese locale
这一阶段,我原本以为:
中文化完成了。
但真正的坑才刚刚开始。
五、第二阶段:中文界面有了,但 HTML 邮件的 Dark Mode 很难看
Pelton 支持 HTML 邮件。
这意味着一个非常现实的问题:
邮件不是普通网页。
不同邮件可能来自:
QQ 邮箱
Outlook
Gmail
企业邮件系统
Marketing 邮件平台
新闻订阅
电商网站
自动生成的 HTML 模板
里面可能同时存在:
inline CSS
背景图片 渐变 品牌色 按钮 Logo SVG 媒体查询
所以 Dark Mode 如果处理得太粗暴,就很容易出现:
背景变黑了,文字也变黑了。
最终:
根本看不清。
六、最开始的办法:颜色白名单
最开始的思路非常直观。
比如:
#000000
#333333
#666666
#FFFFFF
#F8F8F8
看到这些颜色,就进行转换。
但是很快遇到了 TeamViewer 的邮件。
它使用了:
#62656A
这个颜色看起来就是灰色。
但它不是标准的:
#666666
同样,它的背景还出现:
#EDEBE9
这时候我们意识到:
颜色白名单从数学上就不是一个可持续的方案。
RGB 颜色空间是连续的。
你永远不可能把所有:
“看起来像灰色的颜色”
都写进列表。
七、第三阶段:不再猜颜色,而是分析颜色
于是我们换了一个完全不同的思路。
不再判断:
“是不是 #333333?”
而是直接读取浏览器计算后的:
getComputedStyle(element)
然后分析:
相对亮度
饱和度
前景色
背景色
简单来说:
从“颜色名单”升级成“颜色分类算法”。
八、背景先处理,文字后处理
最终采用了两趟处理。
第一趟:
背景。
第二趟:
文字。
为什么一定要这样?
因为:
白色背景
+
黑色文字
如果背景先变成:
深色背景
那么第二步读取文字的时候,就知道:
现在这个文字处于深色背景上。
然后才可以决定:
是否需要把文字提亮。
否则背景和文字各自独立判断,非常容易产生:
黑底黑字。
或者:
白底白字。
九、品牌色不能乱改
又出现了一个问题。
比如 TeamViewer:
#0564C8
这是品牌蓝。
它的亮度其实并不高。
如果单纯按照亮度判断:
它可能被当成深色文字。
于是就会被改成灰色。
品牌色直接没了。
所以又加入了:
饱和度保护。
简单来说:
低饱和度 + 低亮度
更可能是:
普通灰黑文字。
而:
高饱和度
更可能是:
品牌色、链接色、彩色 UI。
因此像:
#0564C8
这种颜色就直接保留。
这个方案比维护:
TeamViewer 蓝
Microsoft 蓝
Steam 蓝
Google 蓝
...
这样的颜色白名单合理得多。
十、为什么不直接修改邮件源码?
这里还有一个很重要的设计原则。
我们不修改:
原始 MIME
原始 HTML
数据库
BodyHTMLSafe
回复内容
转发内容
而是在邮件进入渲染阶段以后:
只修改内存中的 DOM。
这样 Dark Mode 只是:
邮件原文
↓
srcdoc
↓
浏览器渲染
↓
DOM 分析
↓
修改颜色
↓
显示
邮件本身并没有被改掉。
这也意味着:
关闭 Dark Mode 后,原邮件仍然是原来的样子。
十一、TeamViewer:第一次真正证明算法有效
修改完成以后,我们重新拿 TeamViewer 邮件测试。
原来的颜色:
#3B3D40
#62656A
#EDEBE9
#F8F8F8
#0564C8
#FFFFFF
最终:
#3B3D40 → #E5E5E5
#62656A → #C9C9C9
#EDEBE9 → 深色背景
#F8F8F8 → 深色背景
#0564C8 → 保留
#FFFFFF → 根据上下文保留
最终实现:
深色背景 + 清晰正文 + 品牌色保留。
这就是 v2026.3.43。
十二、然后出现了一个更奇怪的问题:邮件能发出去,但已发送是空的
这次问题和 Dark Mode 完全不同。
我从 Pelton 发一封邮件。
结果:
对方收到了。
所以 SMTP:
没问题。
但是:
Pelton 的已发送没有。
更奇怪的是:
QQ 邮箱 Web 端的已发送也没有。
但是 QQ 邮箱:
有投递记录。
这时候很容易认为:
“是不是 Pelton 没同步?”
于是:
刷新
↓
同步
↓
重启
全部试了一遍。
还是没有。
十三、DSH 开始从源码重新追发送链路
这次没有继续猜。
而是直接从源码开始追:
Compose
↓
SendMessage
↓
SMTP
↓
Outbox Worker
↓
Transmit
最终发现:
SMTP 发送成功之后,本来应该还有:
IMAP APPEND
↓
Sent
但 Pelton 原来的代码实际上:
根本没有接上这个 Appender。
也就是说:
SMTP
↓
QQ SMTP
↓
对方收到
这一条路是通的。
但:
SMTP 成功
↓
IMAP APPEND
↓
QQ 已发送
这一条路:
没有真正执行。
十四、为什么 Pelton 有 AppendToSent,却还是没用?
这是整个问题最容易误解的地方。
源码里其实已经存在:
AppendToSent()
但“有这个函数”和“发送邮件的时候调用这个函数”是两回事。
原来的发送流程中:
appendSent == nil
于是:
appendToSent()
直接被跳过。
这就是典型的:
功能代码存在,但没有接线。
十五、接上 IMAP APPEND
于是 DSH 修改发送链路:
SMTP 发送成功
↓
IMAP Connect
↓
Login
↓
找到 Sent 文件夹
↓
APPEND
↓
markSent
而且使用的是同一份:
m.Raw
也就是说:
SMTP 实际发送出去的 MIME 和 IMAP 归档到 Sent 的 MIME 是同一份。
这样可以避免:
“对方收到的邮件”和“自己已发送里的邮件”内容不一致。
十六、然后 QQ 又给我们上了一课
接入 APPEND 后,我以为问题解决了。
结果:
QQ 还是没有已发送。
于是开始查 QQ 的文件夹。
Pelton 原本支持:
Sent
Sent Items
Sent Mail
Gesendet
已发送
已发邮件
但是 QQ Web 显示:
已发送(Sent Messages)
于是问题终于出现了。
Pelton:
找不到 Sent Messages。
找不到目标文件夹:
就不会执行 APPEND。
所以整个过程变成:
SMTP 成功
↓
对方收到
↓
寻找 Sent
↓
找不到
↓
APPEND 不执行
↓
QQ 已发送为空
这和之前的现象完全对应。
十七、这次我们没有继续猜,而是搭了一个 IMAP 仿真服务器
这是整个过程中我觉得最有意思的一步。
DSH 在项目之外搭建了一个:
可控 IMAP 仿真服务器。
然后复刻 Pelton 的 IMAP 行为。
直接观察协议层:
LIST
然后:
SPECIAL-USE
最后:
APPEND
例如:
C> LIST "" "*" RETURN (SPECIAL-USE)
S< * LIST () "/" "Sent Messages"
然后如果匹配成功:
C> APPEND "Sent Messages" (\Seen) {48}
S< OK APPEND completed
这时候就可以确定:
APPEND 确实发出去了。
这已经不是“看源码猜问题”。
而是:
直接看网络协议。
十八、甚至遇到了 Modified UTF-7
IMAP 对非 ASCII 文件夹名称还有一个很容易被忽略的细节。
例如:
已发送
在 IMAP 协议层可能会以 Modified UTF-7 表示。
例如:
&XfJT0ZAB-
好在 Pelton 使用的 Go IMAP 库已经处理了这一层。
因此最终确认:
中文编码本身不是问题。
真正的问题只是:
Sent Messages
没有加入 Pelton 的回退列表。
十九、v2026.3.45:终于把诊断能力也补上了
于是这次不只是修一个字符串。
我们顺便把诊断能力补上。
加入:
Sent Messages
作为 Sent 文件夹的候选名称。
同时增加:
pelton.log
让 GUI 版本也能看到:
SMTP send succeeded
append to Sent: starting
append to Sent: found folder
appended to Sent
如果失败:
append to Sent failed
如果找不到:
special folder not found
available mailboxes="..."
这样以后遇到类似问题,就不需要:
猜。
直接看日志即可。
二十、最终形成了一条完整的调试链
到这里,Pelton 的发送过程就变得非常清晰:
Compose
↓
Outbox
↓
SMTP Send
↓
SMTP 成功
↓
IMAP LIST
↓
寻找 Sent
↓
找到 Sent / Sent Messages
↓
IMAP APPEND
↓
QQ 已发送
而如果任何一步失败:
pelton.log
都应该能够提供证据。
二十一、三个版本记录了整个过程
这次折腾下来,几个版本节点也很有意思。
版本 主要内容
v2026.3.43 DOM 级 Dark Mode 颜色分析
v2026.3.44 接入 IMAP Sent APPEND、中文 Sent 文件夹识别
v2026.3.45 增加 Sent Messages 支持、IMAP 诊断日志、仿真测试
所以这已经不再只是:
“给 Pelton 翻译成中文。”
而是逐渐变成:
“围绕中文用户实际使用场景,对 Pelton 做了一轮本地化和兼容性改进。”
二十二、从源码到自己的 Windows 安装包
这也是这次经历里我觉得比较值得分享的一点。
我们并没有修改完源码就结束。
每一次重要修改之后,都会重新进行:
go build ./...
go vet ./...
go test ./...
pnpm check
pnpm build
确认通过之后,再进行 Windows 构建。
最终生成:
Pelton-v2026.3.45-windows-amd64-installer.exe
于是整个流程实际上变成:
GitHub 源码
↓
git clone
↓
分析源码
↓
提出修改方案
↓
AI Agent 修改
↓
单元测试
↓
静态检查
↓
前端检查
↓
构建
↓
Windows Installer
↓
真实 QQ 邮箱测试
↓
发现问题
↓
继续排查
这才是真正让我觉得有意思的地方。
二十三、这次最大的收获不是“中文 Pelton”
如果只是最终得到一个:
中文版 Pelton
其实没什么特别。
真正让我觉得有价值的是整个过程。
以前我对 AI 写代码的理解是:
“告诉 AI 我要什么,然后让它写。”
但这次体验让我发现:
AI Agent 真正有价值的地方,是可以参与整个工程闭环。
比如:
发现问题
↓
提出假设
↓
分析源码
↓
设计方案
↓
修改
↓
编译
↓
测试
↓
发现新问题
↓
重新建立假设
↓
增加日志
↓
搭建仿真环境
↓
抓协议
↓
最终定位
这和单纯让 AI “写一段代码”其实完全是两回事。
二十四、最让我印象深刻的一件事:不要靠猜
QQ 已发送的问题就是最好的例子。
如果没有源码分析,很容易认为:
QQ 邮箱不支持。
如果没有日志,很容易认为:
Pelton 没刷新。
如果没有 IMAP 仿真,很容易认为:
APPEND 可能失败。
但最后真正的问题其实非常简单:
Sent Messages
没有在候选列表里。
一个字符串。
却花了很多时间才真正证明。
所以这次我最大的感受就是:
调 Bug 最怕的不是问题复杂,而是没有证据。
只要能够不断把问题变成:
有没有调用?
有没有连接?
有没有发送?
服务器返回什么?
到底是哪一步失败?
一个看起来非常玄学的问题,最后都可能变成一个非常具体的问题。
二十五、如果你也想自己修改开源软件
其实这次经历让我觉得:
你不一定需要先成为程序员。
当然,真正修改大型项目还是需要一定的技术基础。
但是现在 AI Agent 可以承担大量:
源码搜索
代码阅读
调用链分析
修改
单元测试
编译
日志分析
协议分析
人更重要的工作反而变成:
描述问题。
以及:
判断什么结果才算正确。
例如这次我始终可以非常明确地告诉 DSH:
“我从 Pelton 发邮件,对方收到了,但是 QQ Web 已发送没有。”
这句话比:
“帮我修一下发邮件的问题。”
有价值太多了。
二十六、最后回到最初的目标
最开始:
我只是想要一个中文 Pelton。
最后:
中文 i18n
↓
HTML Email Dark Mode
↓
DOM 颜色分析
↓
品牌色保护
↓
TeamViewer / AFFiNE 测试
↓
SMTP / IMAP 调试
↓
Sent APPEND
↓
QQ 邮箱兼容
↓
SPECIAL-USE
↓
Modified UTF-7
↓
IMAP 仿真
↓
日志系统
一不小心,就变成了一次完整的开源项目工程实践。
而且这次最有意思的是:
我并不是程序员。
我只是一个觉得:
“这个软件挺好,就是没有中文。”
于是开始折腾。
代码主要由 DSH 完成,但整个过程并不是:
AI 写,我复制。
而是:
我提出问题 → DSH 分析 → 我实测 → 发现问题 → DSH 再分析 → 建立证据 → 修复。
我觉得这可能才是 AI Agent 时代,普通用户参与开源项目的一种新方式。
项目地址
如果你也想试试 Pelton,可以从这里开始:
Pelton 官方网站
Pelton GitHub 源码
官方项目目前仍在持续开发中,Pelton 本身采用 GPL-3.0 开源协议。
如果你准备自己修改,最简单的开始方式就是:
git clone https://github.com/TRC-Loop/Pelton.git
cd Pelton
然后先:
git status
确认工作区干净,再进行第一次完整构建。
不要一上来就改代码。
先让项目:
在你的环境里成功编译一次。
之后再开始你的第一次修改。
写在最后
我以前总觉得:
“不会写代码,就很难参与开源项目。”
这次 Pelton 的经历让我稍微改变了一点看法。
也许现在真正重要的能力,已经不只是:
“我能不能写出这段代码?”
而是:
“我能不能把问题描述清楚,并且和 AI 一起把它验证清楚?”
从中文翻译开始,到最后追到 IMAP 协议层。
这一路其实挺折腾。
但也挺有意思。
而这可能只是我第一次真正意义上“参与修改一个开源项目”。
我和 DSH 一起把 Pelton 改造成中文邮件客户端
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法