邮件自动化是后台系统里最常见的需求之一——注册确认、告警通知、周报推送,几乎都离不开它。Python 标准库自带 smtplib 和 email,不用装任何第三方包就能把邮件从代码里发出去。但"能发"和"发得好"之间,隔着 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']}")
批量发送的几个坑:
- 速率限制:Gmail 每天约 500 封,Outlook 约 300 封。超过会被临时封号。如果量大,考虑专业邮件服务(SendGrid、Mailgun 等)。
- 连接复用:上面代码在整个循环期间保持一条 SMTP 连接,比每封邮件重新连接高效得多。
- 失败处理:生产环境务必加
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 结构,和本文讲的一模一样。