用 Python 发邮件:从纯文本到批量个性化,SMTP 实战全流程

2026-05-27 15 预计阅读时间:1 分钟
来源:realpython.com AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:8 分钟

邮件自动化是后台系统里最常见的需求之一——注册确认、告警通知、周报推送,几乎都离不开它。Python 标准库自带 smtplibemail,不用装任何第三方包就能把邮件从代码里发出去。但"能发"和"发得好"之间,隔着 HTML 格式、附件处理、批量个性化这几道坎。下面逐个拆解。

最简 SMTP 连接:先把一封纯文本邮件发出去

SMTP 协议的核心流程只有三步:连接服务器 → 登录认证 → 发送消息。smtplib.SMTP 把这三步封装成了方法调用。

import smtplib

sender = "you@example.com"
password = "your_app_password"   # 注意:多数邮箱要求用"应用专用密码",不是登录密码
recipient = "friend@example.com"

message = """\
Subject: Hello from Python
From: {sender}
To: {recipient}

This is a plain-text email sent via SMTP.
No HTML, no attachments — just text.
""".format(sender=sender, recipient=recipient)

with smtplib.SMTP("smtp.example.com", 587) as server:
    server.starttls()          # 升级到 TLS 加密通道
    server.login(sender, password)
    server.sendmail(sender, recipient, message)

几点实操提醒:

  • 端口选择:587 是 TLS/STARTTLS 标准端口;465 用于 SSL 直连,对应 smtplib.SMTP_SSL。大部分现代邮箱服务商推荐 587。
  • 应用专用密码:Gmail、Outlook 等已禁止直接用账号密码登录 SMTP,需要在邮箱设置里生成一个"应用密码"。
  • with 语句:确保连接自动关闭,避免资源泄漏。

HTML 邮件与附件:用 email 包构建 MIME 消息

纯文本够用,但告警邮件带个表格、通知邮件带个 Logo,体验会好很多。Python 的 email.mime 系列模块专门处理这类需求。

HTML 正文 + 纯文本备用

邮件客户端千差万别——有的渲染 HTML,有的只显示纯文本。标准做法是同时提供两种格式,让客户端自行选择,这叫 MIMEMultipart("alternative")

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

msg = MIMEMultipart("alternative")
msg["Subject"] = "Weekly Report"
msg["From"] = sender
msg["To"] = recipient

text = "Your weekly report is ready. View it in an HTML-capable client."
html = """\
<html>
  <body>
    <h2>Weekly Report</h2>
    <p>Your weekly report is ready.</p>
    <table border="1">
      <tr><th>Metric</th><th>Value</th></tr>
      <tr><td>Uptime</td><td>99.9%</td></tr>
      <tr><td>Errors</td><td>3</td></tr>
    </table>
  </body>
</html>
"""

msg.attach(MIMEText(text, "plain"))
msg.attach(MIMEText(html, "html"))

顺序很重要:先 attach 纯文本,再 attach HTML。MIME 标准规定,最后一部分是"首选"呈现方式,客户端不支持 HTML 时会回退到前面的纯文本。

添加附件

附件的本质是把二进制文件编码成 base64,塞进 MIMEBase 并设置合适的 Content-Type。更简便的做法是用 email.mime.application.MIMEApplication

from email.mime.application import MIMEApplication

with open("report.pdf", "rb") as f:
    attachment = MIMEApplication(f.read(), Name="report.pdf")

attachment["Content-Disposition"] = 'attachment; filename="report.pdf"'
msg.attach(attachment)

发送时,把 msg 转成字符串即可:

with smtplib.SMTP("smtp.example.com", 587) as server:
    server.starttls()
    server.login(sender, password)
    server.sendmail(sender, recipient, msg.as_string())

批量个性化邮件:模板 + 循环发送

给 200 个联系人发同一封邮件,但每封都要带上对方的名字——这就是"批量个性化"。核心思路:准备一个收件人列表 + 模板字符串,循环生成并发送。

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

contacts = [
    {"name": "Alice", "email": "alice@example.com"},
    {"name": "Bob",   "email": "bob@example.com"},
    {"name": "Carol", "email": "carol@example.com"},
]

SMTP_HOST = "smtp.example.com"
SMTP_PORT = 587
SENDER = "you@example.com"
PASSWORD = "your_app_password"

template_html = """\
<html>
  <body>
    <p>Hi {name},</p>
    <p>We've just rolled out a new feature we think you'll love.</p>
    <p>Check it out and let us know your thoughts!</p>
  </body>
</html>
"""

template_text = "Hi {name}, we've just rolled out a new feature. Check it out!"

with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
    server.starttls()
    server.login(SENDER, PASSWORD)

    for contact in contacts:
        msg = MIMEMultipart("alternative")
        msg["Subject"] = "New feature for you"
        msg["From"] = SENDER
        msg["To"] = contact["email"]

        msg.attach(MIMEText(template_text.format(name=contact["name"]), "plain"))
        msg.attach(MIMEText(template_html.format(name=contact["name"]), "html"))

        server.sendmail(SENDER, contact["email"], msg.as_string())
        print(f"Sent to {contact['email']}")

批量发送的几个坑:

  1. 速率限制:Gmail 每天约 500 封,Outlook 约 300 封。超过会被临时封号。如果量大,考虑专业邮件服务(SendGrid、Mailgun 等)。
  2. 连接复用:上面代码在整个循环期间保持一条 SMTP 连接,比每封邮件重新连接高效得多。
  3. 失败处理:生产环境务必加 try/except,记录失败地址以便重试,而不是让一个异常中断整批发送。

实战检查清单

上线前逐条确认:

项目 说明
TLS/SSL 必须加密传输,绝不用明文 SMTP 端口 25
应用密码 不硬编码账号登录密码;用环境变量或密钥管理工具存应用密码
HTML + 纯文本 同时提供两种格式,保证兼容性
附件 Content-Type 按实际文件类型设置(PDF 是 application/pdf,图片是 image/png 等)
发送速率 单连接批量发送,控制每秒/每天数量,避免触发服务商限制
错误重试 捕获 smtplib.SMTPException,记录失败地址,支持断点续发
隐私合规 批量邮件不要把所有收件人地址放进同一个 To 字段——每人单独一封,或用 BCC

Python 标准库已经覆盖了邮件发送的绝大部分场景。如果你的需求只是定时推送报告、告警通知、注册确认,smtplib + email.mime 完全够用。量级到了每天上千封、需要追踪打开率和点击率时,就该切换到 SendGrid/Mailgun 这类专业平台了——但底层 SMTP 协议和 MIME 结构,和本文讲的一模一样。


相关推荐