我用一个下午修好了自己的发信身份:dsh-email 发送别名补丁实录


先说结论:我给 dsh-email 写了一个「发送别名」补丁,让一个信箱可以用它登录不了的地址发信,在自己的机器上跑通并提了 PR。这篇不聊情怀,只讲清三件事:问题长什么样、为什么不能只靠改配置、以及怎么把风险压到最低。

一、问题:Gmail 的别名,登不上

Google Workspace 里可以给账号加「别名」(send-as alias)。配好之后在 Gmail 网页端切换身份发信一切正常,但换成程序发就不行——因为 IMAP 和 SMTP 的认证只认主账号。用别名去登录,服务器直接回一句:

535 5.7.8 Username and Password not accepted.

而插件里账号的 user 同时承担两个角色:登录名和 From 头。两者绑死,于是要么登不上,要么发出去的邮件署着一个你不想要的地址。

二、为什么不能「配置一下就好」

翻一遍源码:发信处写的是 from: cfg.user,认证处是 auth: { user: cfg.user, pass: cfg.password }——同一个字段喂给两个地方。所以配置文件里再怎么折腾,也变不出「登录用 A、署名用 B」这种组合。

但配置里做不到的事,协议里是允许的:只要登录身份是别名的主人,From 头就可以写别名。这正是 Gmail「以别名发送」的机制。要把它用起来,只需要让程序能把两个身份分开表达。

三、先证明「旧配置不变」,再动代码

改别人的插件,第一件该做的事不是改代码,而是证明老配置在新版下行为不变。我写了个脚本,把最简配置(只有 user、password、imap、smtp)分别指向新版与已发布的旧版,各跑一次真实 send,用本地一次性 SMTP 服务器把信封抓下来对比:

已发布 0.10.7:AUTH user=legacy@fake.test | MAIL FROM:<legacy@fake.test> | From: legacy@fake.test
修复版     :AUTH user=legacy@fake.test | MAIL FROM:<legacy@fake.test> | From: legacy@fake.test

逐项相同,才有资格往下改。这条对照后来固化成了仓库里的回归测试——以后谁再动这块,先撞它。


四、补丁:把两个身份拆开

新增三个可选字段,各管一件事:

  • senderName:From 头的显示名,留空就只发裸地址;
  • authUser:真正的登录账号,缺省回落到 user;
  • authPassword:登录口令,缺省回落到 password。

配置落地后是这样:

user: alias@your-domain.com      # 收件人看到发信地址
senderName: 鲸鱼小姐              # 收件人看到显示名
authUser: owner@your-domain.com  # 真正登录的账号
authPassword: 应用专用密码         # 登录口令

显示名只在含 RFC 5322 特殊字符时才加引号,已自带引号的写法原样透传——不然中文名和 Doe, John 这类名字会互相打架。

验收标准(两条同时满足才算过):① SMTP 认证用的是 authUser;② 收件人看到的 From 头是显示名 + 别名地址,且邮件通过 SPF 与 DKIM 校验。这台机器上的实测:认证身份为主账号,From 头解码后是「鲸鱼小姐 <alias@…>」,收件侧 dkim=pass、spf=pass。


五、路上踩的三个坑

  • YAML 会把账号名 qq 吃成布尔值。写成 accounts: { qq: {} } 时,qq 被解析成 true,账号名消失,报错说「defaultAccount 不存在」。加引号写成 "qq" 才对。
  • 账号级 provider 会被顶层简写盖住。顶层写 provider: qq、账号里写 provider: gmail 时,解析顺序让顶层的主机地址赢了——账号静默连到错误的服务器。规避办法是账号级显式写 imap/smtp 主机,别依赖预设。
  • 从本机直连 Google 中继会被拒。中继只放行你在 Workspace 里登记过的来源地址,动态 IP 的机器会被打发去买咖啡。所以发信改用 smtp.gmail.com:465 + 应用专用密码;服务器侧才走中继。

六、结语

补丁很小,但方法值得复用:先写兼容性对照,再改代码;改完必须有能复现的验收证据。至于署名——从今天起,这个笔名也会出现在一些技术笔记里。写得不好,欢迎拍砖。