因为smtplib只传输原始字节流,不处理MIME结构,手动拼接易破坏boundary导致附件丢失或HTML被当纯文本;必须用email模块的MIMEMultipart等类构建符合RFC 2822的邮件体,正确设置层级、编码、Content-Disposition头及SMTP配置。

为什么直接用 smtplib.sendmail() 会丢失附件或HTML渲染?
因为 smtplib 本身不处理邮件结构,它只负责把原始字节流发出去。如果你手动拼接 HTML 内容和附件二进制数据,很容易破坏 MIME 边界(boundary),导致收件端无法识别附件或把 HTML 当纯文本显示。
真正该用的是 email 标准库里的 MIMEMultipart 和 MIMEText、MIMEBase 等类来构造符合 RFC 2822 的多部分邮件体。
- 必须用
MIMEMultipart("mixed")作为根容器,再嵌套MIMEMultipart("alternative")放 HTML/plain 文本 - 附件要用
MIMEApplication或MIMEBase包装,并调用set_payload()+encoders.encode_base64() -
Content-Disposition: attachment头不能漏,否则 Outlook 等客户端可能内联显示而非下载
怎么正确添加 HTML 正文和多个附件?
关键在于层级:外层 mixed → 中层 alternative(放 text/html + text/plain)→ 外层同级追加附件节点。顺序错了,附件就变成正文的一部分。
示例片段(注意变量名和路径):
立即学习“Python免费学习笔记(深入)”;
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
import smtplib
<p>msg = MIMEMultipart("mixed")
msg["Subject"] = "测试带附件的HTML邮件"
msg["From"] = "sender@example.com"
msg["To"] = "receiver@example.com"</p><h1>构造HTML正文部分</h1><p>html_part = MIMEMultipart("alternative")
html_body = MIMEText("<h2>你好</h2><p>这是 <strong>HTML</strong> 邮件</p>", "html", "utf-8")
plain_body = MIMEText("这是纯文本备用内容", "plain", "utf-8")
html_part.attach(plain_body)
html_part.attach(html_body)
msg.attach(html_part)</p><h1>添加附件(假设文件在当前目录)</h1><p>for file_path in ["report.pdf", "data.xlsx"]:
with open(file_path, "rb") as f:
part = MIMEApplication(f.read(), _subtype="pdf" if file_path.endswith(".pdf") else "vnd.openxmlformats-officedocument.spreadsheetml.sheet")
part.add_header("Content-Disposition", "attachment", filename=file_path)
msg.attach(part)</p><h1>发送</h1><p>server = smtplib.SMTP("smtp.example.com", 587)
server.starttls()
server.login("sender@example.com", "app_password")
server.send_message(msg)
server.quit()</p>常见报错和对应修复点
smtplib.SMTPDataError: (554, b'Transaction failed') —— 多半是附件没设 Content-Disposition 或文件路径不存在;
UnicodeEncodeError: 'ascii' codec can't encode characters —— HTML 字符串没指定 charset="utf-8",或 MIMEText 初始化时漏了第三个参数;
- 所有
MIMEText必须显式传入"utf-8"编码,不能依赖默认 - 附件文件名含中文?用
Header类编码:from email.header import Header; part.add_header("Content-Disposition", "attachment", filename=Header("中文.pdf", "utf-8").encode()) - Gmail 拒绝登录?改用应用专用密码(App Password),且确保账户开启两步验证
附件太大或 HTML 含图片资源怎么办?
smtplib 对单次发送的数据量没硬限制,但邮箱服务商有(Gmail 是 25MB)。超限会直接断连,不会抛出明确异常,只会卡在 send_message() 或返回模糊错误码。
- 超过 10MB 的附件建议转用云存储链接 + 文本说明,而非硬塞进邮件
- HTML 里引用本地图片?不行 —— 收件方看不到。要转成内联 base64(用
MIMEImage),或托管到公网 URL - 避免在 HTML 中写
<img src="file:///...">,这种路径仅发件人本地有效
边界、编码、头字段、服务商限制——这四点漏掉任何一环,附件就大概率消失或 HTML 变乱码。


















