踩坑实录:邮件编码报错排查与修复

本文来自真实客户反馈的排查全程。若你在「发送测试邮件」时遇到
UnicodeEncodeError ascii 编码报错,直接看坑一的「修复与临时规避」。
修复已合入主分支,新版程序发布后即包含;发布前的临时规避方法:将
Windows 计算机名改为英文(或等新版)。

关联提交:1358dee(收件人显示名提取)· caa69de → c0534b9(EHLO 修复,合并后推送)

坑一:客户中文计算机名导致发送邮件崩溃

现象

客户在 Web GUI「邮件通知」面板点「发送测试邮件」,报错:

测试失败:UnicodeEncodeError: 'ascii' codec can't encode characters
in position 5-7: ordinal not in range(128)

根因

smtplib 的所有协议命令(EHLO / MAIL FROM / RCPT TO)发送时都执行 .encode('ascii')。smtp.ehlo() 默认取 socket.getfqdn()——即本机 Windows 计算机名。客户的计算机名是中文:

EHLO 张三丰
01234 ↑
     position 5-7 = 三个中文字的机名

"EHLO " 恰好占 position 0-4,报错位置 5-7 直接对应机名前三个中文字。我们所有开发机/服务器机器名全英文,此坑在本地永远无法复现。

排查过程(含弯路)

  1. 先查 ai-site 的 mailer——嫌疑起点合理(发邮件报错),但「测试失败」文案实际属于 ai-agent Web GUI(app.js:975),产品搞错了
  2. 复现 build_message:Subject 用 Header(subject, "utf-8")、From 用 formataddr(("AI SSH Agent", sender)),含中文收件人显示名时 as_string() 竟然正常——差点误判"无 bug"
  3. 唯一未覆盖的路径 = SMTP 协议命令层,缩小范围后命中

真机复现手法(无副作用)

连真实 SMTP 服务器、只 EHLO 不登录:

import smtplib
s = smtplib.SMTP_SSL('smtp.qq.com', 465, timeout=10,
                     local_hostname='张三的电脑')   # 模拟中文机名
s.ehlo()   # → UnicodeEncodeError: ... position 5-9(5 字机名)

position 随模拟机名字数同步变化,根因坐实。

修复(c0534b9)

  1. 新增 _helo_name(user):EHLO 名改用发件邮箱域名(user@qq.com → qq.com,恒 ASCII、符合 RFC);无 @ 时回退 localhost
  2. SMTP_SSL(..., local_hostname=_helo_name(user)),STARTTLS 分支同样处理
  3. 收件人信封发送前加 isascii() 校验:张三@qq.com(无显示名、纯中文前缀,正则提取不出来)这类输入给出明确中文报错,不再裸爆编码错误

验证

  • 单元:_helo_name 域名提取 / 回退逻辑、extract_addr 显示名剥离
  • 端到端:真实 SMTP 两封(中文机名模拟场景 + 合并回归),均收到

教训

  • SMTP 三个 ascii 爆点清单:EHLO(本机机名)、MAIL FROM(发件人)、RCPT TO(收件人);Subject/正文走 Header/utf-8 不受影响
  • 同一报错文案可能来自完全不同的字段,position 数值是最强定位线索(5-7 反推三个中文字)
  • 客户环境多样性(中文机名在国内极常见)必须进入测试考量;开发环境全英文等于天然免疫此类问题

坑二:rebase 冲突中 theirs/ours 方向与直觉相反

现象

推送修复时远端已有另一份同问题修复(1358dee,收件人显示名提取),git pull --rebase 冲突。按 merge 的直觉执行 git checkout --theirs 想拿远端版——拿回来的是自己的旧版本,若直接 continue 会把远端修复覆盖丢失。

正确语义

场景 ours theirs
merge 冲突 当前分支(我) 被合并方(远端)
rebase 冲突 远端基准(HEAD 已在远端提交之上) 我方被重放的提交

rebase 时 HEAD 已经站在远端之上重放我方 commit,语义整体反转。

识别手法(防再犯)

  1. 拿不准就别猜:git log --oneline origin/master -3 看远端到底改了什么
  2. checkout 后立即 grep 双方特征符号核对版本:grep -n "def extract_addr\|def _helo_name" ai_agent/mailer.py——远端版有前者无后者,一验便知
  3. 本例正确路径:git checkout --ours(远端基准)→ 叠加本地独有的 EHLO 修复 → git add → GIT_EDITOR=true git rebase --continue → push

关联教训

  • 同一 bug 可能已有远端修复(另一会话/协作者从不同角度修的):解决冲突前先读远端提交说明,判断该"覆盖"还是"合并互补"。本例 1358dee 修收件人显示名提取、本地修 EHLO 机名,互不覆盖,最终 c0534b9 = 两者叠加
  • 长会话改动先 commit 再 pull --rebase,避免半成品卷入冲突
  • rebase --continue 若提示"未标记 resolved",是还有文件未 git add