最近我折腾了一个很有意思的开源项目: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 协议层。

这一路其实挺折腾。

但也挺有意思。

而这可能只是我第一次真正意义上“参与修改一个开源项目”。