登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  python教程

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

这里的三个头部字段只是消息内容的一部分,不代表已经完成投递。尤其不要把服务器地址、账号密码或重试策略塞进这个构造函数。

EmailMessage 包含正文和 MIME 附件的静态结构关系
图1:EmailMessage 正文与 MIME 附件的静态结构关系;这是结构说明图,不是运行截图。

用 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 发送,连接、认证、超时和重试应在调用方处理。这样单元测试可以只检查消息对象或序列化结果,而不必每次都连接真实邮件服务器。

文本正文和附件 bytes 经过 EmailMessage 构造并序列化到发送层的边界图
图2:从内容输入到消息字节的职责边界;这是操作示意图,不是邮件客户端截图。

常见边界与排查顺序

现象优先检查处理方向
附件能发出但文件类型不对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 或其他发送组件负责。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>