Python email.message.EmailMessage 怎样添加 MIME 附件
来源:17golang原创
时间:2026-10-28 12:41:45 170浏览 收藏
Python 里添加邮件附件,推荐把“消息构造”和“邮件发送”分开:用 EmailMessage 组织正文与 MIME 附件,用 as_bytes() 生成消息字节,最后再交给 SMTP 或其他发送层。这样不会把本地文件读取、附件类型和服务器连接混在一个函数里。
官方地址:https://docs.python.org/3/library/email.message.html
最小做法是先用 set_content() 写入正文,再调用 add_attachment() 追加附件。二进制附件通常传入 bytes,并显式提供 maintype、subtype 和 filename;这些信息决定 MIME 部件怎样被描述和还原。
先分清 EmailMessage 与 SMTP
email 包负责邮件对象模型、头部、正文、MIME 子消息和序列化;它本身不负责连接 SMTP 服务器。EmailMessage 可以看成一个有序的消息容器,简单消息的 payload 可以是文本或字节,multipart 消息则由多个子消息组成。
因此,本文的函数只返回一个 EmailMessage。调用方可以选择 as_bytes() 得到完整字节,也可以把对象交给支持现代邮件 API 的发送代码。
先添加纯文本正文
没有附件时,消息可以保持为普通文本部件。set_content() 负责把字符串放入正文,并根据消息的 policy 完成必要的内容头处理。
from email.message import EmailMessage
def build_message(subject: str, sender: str, recipient: str) -> EmailMessage:
# 先建立消息头;EmailMessage 只描述消息,不负责 SMTP 连接。
message = EmailMessage()
message["Subject"] = subject
message["From"] = sender
message["To"] = recipient
# 正文使用字符串,后续追加附件时会自动进入 multipart 结构。
message.set_content("这是正文,附件将在下一步加入。")
return message
这里的三个头部字段只是消息内容的一部分,不代表已经完成投递。尤其不要把服务器地址、账号密码或重试策略塞进这个构造函数。

用 add_attachment 添加二进制附件
二进制附件的关键是把“字节内容”和“媒体类型”同时交给 add_attachment()。例如 PDF 可以写成 maintype="application"、subtype="pdf",文件名放在 filename 中。
from email.message import EmailMessage
def attach_pdf(message: EmailMessage, pdf_bytes: bytes, filename: str) -> None:
# bytes 表示附件内容;MIME 类型描述接收方应如何理解这些字节。
message.add_attachment(
pdf_bytes,
maintype="application",
subtype="pdf",
filename=filename,
)
message = EmailMessage()
message["Subject"] = "月度报告"
message["From"] = "report@example.test"
message["To"] = "reader@example.test"
message.set_content("请查看附件中的月度报告。")
attach_pdf(message, b"%PDF-demo", "monthly-report.pdf")
添加后,消息会从单一正文变成包含多个部件的 MIME 结构。filename 是附件对外显示的名称,不是服务器上的路径;不要把 /Users/... 这类本地路径直接作为文件名。
处理文本附件与字节附件
add_attachment() 可以处理不同类型的 payload,但调用者仍要明确数据类型。文本附件适合传入字符串并指定字符集,二进制内容则应保持为 bytes,不要先用错误的编码转换成字符串。
def add_text_note(message: EmailMessage, text: str, filename: str) -> None:
# 文本附件保留字符集信息,接收方才能按文本而不是二进制理解它。
message.add_attachment(
text,
subtype="plain",
charset="utf-8",
filename=filename,
)
def add_binary(message: EmailMessage, payload: bytes, filename: str) -> None:
# 二进制内容不做隐式 decode,类型由 maintype/subtype 明确给出。
message.add_attachment(
payload,
maintype="application",
subtype="octet-stream",
filename=filename,
)
如果附件是图片、压缩包或自定义格式,maintype 和 subtype 应来自实际媒体类型,而不是一律写成 PDF。类型写错时,邮件可能仍能发送,但接收端的预览和下载行为会变得不可靠。
序列化并交给发送层
当消息结构完成后,用 as_bytes() 生成完整的 MIME 字节。序列化可能补齐边界等结构信息,所以应在内容不再变化时调用,并把返回值交给后续发送层。
from email.message import EmailMessage
def make_payload() -> bytes:
# 构造阶段只负责消息树,发送阶段只接收最终字节。
message = EmailMessage()
message["Subject"] = "数据导出"
message["From"] = "export@example.test"
message["To"] = "ops@example.test"
message.set_content("导出任务已完成,请下载附件。")
message.add_attachment(
b"id,name\n1,Alice\n",
maintype="text",
subtype="csv",
filename="users.csv",
)
# as_bytes() 返回要交给发送组件的消息内容,不会主动发送邮件。
return message.as_bytes()
如果要用 smtplib 发送,连接、认证、超时和重试应在调用方处理。这样单元测试可以只检查消息对象或序列化结果,而不必每次都连接真实邮件服务器。

常见边界与排查顺序
| 现象 | 优先检查 | 处理方向 |
|---|---|---|
| 附件能发出但文件类型不对 | maintype、subtype | 按真实媒体类型填写,不用文件扩展名猜测全部语义 |
| 中文文本附件乱码 | 传入值是否为字符串、charset 是否明确 | 文本附件保留 UTF-8 字符集,二进制附件保持 bytes |
| 只看到正文没有附件 | 是否调用了 add_attachment()、是否在序列化前修改对象 | 先完成所有部件,再调用 as_bytes() |
| 把本地路径暴露给收件人 | filename 是否误用了完整路径 | 只传递收件人需要看到的文件名 |
排查时可以先看 message.is_multipart(),再按消息树读取各个部件。判断结构比直接搜索序列化字符串更稳定,因为 MIME 边界可能由序列化阶段补齐。
小结
Python 添加 MIME 附件的核心组合只有三步:用 EmailMessage 创建消息,用 set_content() 设置正文,用 add_attachment() 追加带类型和文件名的 payload。内容完成后再调用 as_bytes(),把“构造消息”和“发送消息”保持为两个职责。
这套写法适合报告、导出文件和通知类邮件。如果需求涉及 SMTP 连接池、认证、重试或投递状态,应在发送层单独设计,不要继续向消息构造函数堆参数。
相关问题
EmailMessage 添加附件后为什么会变成 multipart?
因为正文和附件是多个 MIME 部件,EmailMessage 需要用 multipart 容器表达它们之间的层级关系。
add_attachment 一定要写 maintype 和 subtype 吗?
对 bytes 附件应显式提供,避免接收端无法判断内容类型;文本附件则应至少明确文本类型和字符集。
as_bytes() 会不会发送邮件?
不会。它只把消息对象序列化为字节,连接服务器和实际投递由 SMTP 或其他发送组件负责。
-
369 收藏
-
344 收藏
-
464 收藏
-
327 收藏
-
349 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习