R / Richie全部文章 ↑

Python · 5 分钟阅读

`smtplib` & `email`:发送邮件

目录


Python 发送邮件靠两个标准库:

  • smtplib:连接 SMTP 服务器、发 SMTP 命令。
  • email:构造规范的邮件内容(文本 / HTML / 附件 / 标题 / 编码)。

⚠️ 永远不要把账号密码写死在源码里,用环境变量或配置文件。
现代邮箱(Gmail / Outlook / QQ)几乎都需要 TLS,明文 25 端口大多
已经废弃。


1. 准备

1.1 邮箱授权码

主流邮箱需要 授权码(不是登录密码):

  • 163:设置 → POP3/SMTP/IMAP → 客户端授权密码
  • QQ:设置 → 账户 → POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 → 开启并生成授权码
  • Gmail:开启两步验证 → 应用专用密码
  • Outlook:直接用账号密码(开启 SMTP)

1.2 服务器 / 端口

服务 主机 推荐端口(SSL) 备选(STARTTLS)
163 smtp.163.com 465 或 994 25 / 587
QQ smtp.qq.com 465 或 587 —
Gmail smtp.gmail.com 465 587
Outlook smtp.office365.com 587 (STARTTLS) —
自建 视情况 465 587

端口 465 = SMTP over SSL(先 TLS 再 SMTP);端口 587 = STARTTLS
(先明文 SMTP,再 starttls() 升级)。生产环境二选一,绝不直接 25 明文。


2. 用 email.message.EmailMessage 构造邮件

from email.message import EmailMessage

msg = EmailMessage()
msg["Subject"] = "Test email"
msg["From"]    = "python@example.com"
msg["To"]      = "demo@example.com"
msg.set_content("这是一封纯文本测试邮件。")

不要再自己拼 From: ...\nTo: ...\n 字符串——EmailMessage 会处理
编码、换行、quoted-printable 等所有坑。

2.1 HTML 邮件 + 纯文本 fallback

msg.set_content("你的邮箱不支持 HTML,请用纯文本查看。")
msg.add_alternative("<h1>Hello</h1><p>这是一封 <b>HTML</b> 邮件。</p>", subtype="html")

2.2 附件

msg.add_attachment(
    open("report.pdf", "rb").read(),
    maintype="application",
    subtype="pdf",
    filename="report.pdf",
)

3. 发送:示例

import smtplib
import os
from email.message import EmailMessage


def make_message() -> EmailMessage:
    msg = EmailMessage()
    msg["Subject"] = "Test email from wlh"
    msg["From"]    = "python@example.com"
    msg["To"]      = "demo@example.com"
    msg.set_content("this is a testing")
    return msg


def send_via_ssl(msg: EmailMessage) -> None:
    host = "smtp.163.com"
    user = "python@example.com"
    password = os.environ["SMTP_PASSWORD"]      # 授权码

    with smtplib.SMTP_SSL(host, 465, timeout=10) as s:
        s.login(user, password)
        s.send_message(msg)


def send_via_starttls(msg: EmailMessage) -> None:
    host = "smtp.qq.com"
    user = "python@example.com"
    password = os.environ["SMTP_PASSWORD"]

    with smtplib.SMTP(host, 587, timeout=10) as s:
        s.ehlo()
        s.starttls(context=None)              # 升级为 TLS
        s.login(user, password)
        s.send_message(msg)


if __name__ == "__main__":
    send_via_ssl(make_message())

with smtplib.SMTP(...) 会自动 quit(),不要忘记。


4. send_message vs sendmail

方法 说明
s.send_message(msg) 推荐,接受 EmailMessage
s.sendmail(from, to, str) 接受字符串,已不推荐

sendmail 仍能用,但你要自己处理 RFC 5322 格式;send_message 帮你做
对的事情。


5. 常见问题

  • SMTPAuthenticationError:账号 / 授权码不对,或没启用 SMTP 服务。
  • SMTPDataError 554:被识别为垃圾邮件。检查 From / Subject / 内容;
    减少链接/图片。
  • 中文标题乱码? EmailMessage 会自动用 Header 编码;如果你手写
    字符串
    到 msg["Subject"],要 Subject = "=?utf-8?B?...?="。
  • 收件人多个? 逗号分隔:msg["To"] = "a@x.com, b@y.com" 或
    s.send_message(msg, to_addrs=["a@x.com", "b@y.com"])。
  • 附件中文名乱码? 用 EmailMessage.add_attachment(filename="报告.pdf")
    时不要自己编码,库已经处理;如果你手写字符串才会乱。
  • 连接超时? 加 timeout=10;考虑公司网络是否要代理。
  • SSL 报错:smtplib.SMTP_SSL 默认校验证书;certifi 缺失或证书过期
    时会抛 ssl.SSLCertVerificationError。升级 certifi 或 pip install --upgrade certifi。

6. 用 yagmail / emails 等第三方库(可选)

如果项目里频繁发邮件,可以省事:

  • yagmail:Gmail 专用,几行就能发带附件的邮件。
  • emails / premailer:HTML 邮件 + 内联 CSS。
  • flask-mail:Flask 框架集成。

简单脚本直接 smtplib + email 即可,无需引入依赖。


7. 小结

  • smtplib 连服务器 + 发 SMTP 命令;email 拼装邮件内容。
  • 端口 465 SSL 或 587 STARTTLS 二选一,不要用 25 明文。
  • 用 EmailMessage + send_message,别再手拼 From: ... 字符串。
  • 账号 / 授权码用环境变量或 .env 注入,不要写进源码。
  • 收件人 / 主题 / 内容别像垃圾邮件,否则会被 554 拒发。