踩坑实录:邮件编码报错排查与修复
本文来自真实客户反馈的排查全程。若你在「发送测试邮件」时遇到
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 直接对应机名前三个中文字。我们所有开发机/服务器机器名全英文,此坑在本地永远无法复现。
排查过程(含弯路)
- 先查 ai-site 的 mailer——嫌疑起点合理(发邮件报错),但「测试失败」文案实际属于 ai-agent Web GUI(
app.js:975),产品搞错了 - 复现
build_message:Subject 用Header(subject, "utf-8")、From 用formataddr(("AI SSH Agent", sender)),含中文收件人显示名时as_string()竟然正常——差点误判"无 bug" - 唯一未覆盖的路径 = 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)
- 新增
_helo_name(user):EHLO 名改用发件邮箱域名(user@qq.com→qq.com,恒 ASCII、符合 RFC);无@时回退localhost SMTP_SSL(..., local_hostname=_helo_name(user)),STARTTLS 分支同样处理- 收件人信封发送前加
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,语义整体反转。
识别手法(防再犯)
- 拿不准就别猜:
git log --oneline origin/master -3看远端到底改了什么 - checkout 后立即 grep 双方特征符号核对版本:
grep -n "def extract_addr\|def _helo_name" ai_agent/mailer.py——远端版有前者无后者,一验便知 - 本例正确路径:
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