xml附件必须用mimemultipart封装,读为bytes后用mimeapplication包裹并设rfc2231编码的content-disposition头,避免中文路径和乱码。

XML文件作为附件发送前必须用 email.mime.multipart.MIMEMultipart 封装
直接用 smtplib.SMTP.sendmail() 发纯文本邮件没问题,但发附件必须走 MIME 协议分段封装。XML 文件不是纯文本邮件体的一部分,而是独立的二进制(或文本)部件,不封装就等于把 XML 内容硬塞进邮件头里——收件方看到的是乱码或被截断。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 用
MIMEMultipart("mixed")创建根容器,它能同时容纳正文和附件 - XML 文件需先读成
bytes(即使内容是 UTF-8 文本),再用MIMEApplication或MIMEText包裹;推荐MIMEApplication,它默认设Content-Transfer-Encoding: base64,兼容性更好 - 务必设置
add_header("Content-Disposition", "attachment", filename="data.xml"),否则 Outlook 等客户端可能不识别为可下载附件
smtplib.SMTP 发送时中文路径或文件名会出错
错误现象常见为 UnicodeEncodeError: 'ascii' codec can't encode characters,根源是 Python 2/3 混用、SMTP 库底层调用系统编码失败,或没对 filename 做 RFC 2231 编码。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 读取 XML 文件时显式指定编码:
with open("配置.xml", "rb") as f: xml_bytes = f.read()—— 注意是"rb",避免自动 decode - 附件名不要直接传中文字符串给
add_header;改用email.utils.encode_rfc2231("配置.xml", "utf-8"),再拼进 header - 如果用
pathlib.Path构造路径,确保路径对象本身不含未转义的空格或特殊字符,Windows 下反斜杠要双写或用正斜杠
XML 内容含特殊字符(如 &、)不影响附件发送,但影响内嵌 XML 的正文渲染
这是个典型混淆点:附件是独立二进制块,XML 里的 & 或 CDATA 段完全不会被 SMTP 解析;只有当你把 XML 当作 HTML 正文 MIMEText(..., "html") 插入时,浏览器才会尝试解析标签,导致显示异常甚至 XSS 风险。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 坚持“附件归附件,正文归正文”:XML 内容绝不拼进
MIMEText的 body 字符串中 - 如果需要在邮件正文里提示内容概要,只写纯文本描述,例如
"已附上订单数据(data.xml),共 12 行,含 <order> 节点"</order>—— 这里的<order></order>是字面量,不是 HTML 标签 - 检查 XML 是否真实有效(可用
xml.etree.ElementTree.parse()加载测试),无效 XML 虽然也能发,但收件方打不开会误以为是传输问题
使用 gmail.com 或企业邮箱时,smtplib.SMTP_SSL 和端口选错直接连接拒绝
现象是 smtplib.SMTPAuthenticationError 或 ConnectionRefusedError,不是密码错,而是协议/端口不匹配。Gmail 已禁用“低安全性应用”,必须开 App Password;而很多企业 Exchange 要求 STARTTLS + 587,不是 465。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- Gmail 必须用
smtplib.SMTP_SSL("smtp.gmail.com", 465)或smtplib.SMTP("smtp.gmail.com", 587)+.starttls();后者更通用,但需手动调ehlo()和starttls() - 认证凭据必须是 App Password(16位),不是账号密码;开启位置在 Google 账号 → 安全 → 两步验证 → App passwords
- 企业邮箱优先查 IT 文档确认 SMTP 地址、是否强制 TLS、是否需域账号格式(如
user@company.com而非user)
最易被忽略的是附件名编码和 MIME 类型声明——很多人测试时用英文文件名成功了,换中文就崩,却去查 SMTP 配置;其实问题压根不在网络层,在那一行 add_header("Content-Disposition", ...) 里没做 RFC 2231 编码。









