Python SMTP 邮件发送入门到进阶

SMTP(Simple Mail Transfer Protocol)是互联网上最基础的邮件发送标准协议,不管是个人邮箱、企业邮箱还是服务端告警系统,底层发送逻辑几乎都绕不开它。

Python 内置了两个核心模块无缝对接:

  • email负责组装“长什么样的邮件”,处理文本、HTML、附件、图片嵌入、邮件头等所有内容
  • smtplib负责把组装好的邮件“送出去”,建立SMTP连接、登录、传输

1. 准备工作:确认你的邮箱支持SMTP

在写代码前,必须先搞定邮箱的SMTP配置:

  1. 开启SMTP服务:登录邮箱后台(如QQ邮箱、163、Gmail),在“设置-账户-IMAP/SMTP/POP3”里开启
  2. 获取授权码:大部分第三方SMTP客户端(包括我们的Python脚本)不能直接用登录密码,必须用邮箱单独生成的「授权码」「应用专用密码」
  3. 记录SMTP信息:找服务商给的SMTP服务器地址、端口(明文/TLS/SSL各有不同)

2. 第一封:纯文本邮件

2.1 组装邮件内容

email.mime.text.MIMEText 就行,这是最基础的MIME(多用途互联网邮件扩展)类型:

from email.mime.text import MIMEText

# 参数依次是:正文内容、MIME子类型、编码
msg = MIMEText('你好!这是一封用Python写的测试邮件😎', 'plain', 'utf-8')

2.2 建立连接并发送

smtplib.SMTP 建立明文连接(默认25端口),调试模式开1可以看详细的协议交互过程:

import smtplib

# 替换成自己的配置
from_addr = input('你的邮箱: ')
auth_code = input('授权码/应用专用密码: ')
to_addr = input('收件人邮箱: ')
smtp_server = input('SMTP服务器地址(比如smtp.qq.com): ')

with smtplib.SMTP(smtp_server, 25) as server:
    server.set_debuglevel(1)  # 显示SMTP交互日志
    server.login(from_addr, auth_code)  # 登录
    # sendmail参数:发件人、收件人列表、字符串格式的邮件
    server.sendmail(from_addr, [to_addr], msg.as_string())

3. 邮件不够友好?补全邮件头

上面的邮件发出去后,收件人可能只会看到一串邮箱地址,没有主题、没有发件/收件昵称,得用 email.header.Headeremail.utils.formataddr 完善:

from email.header import Header
from email.utils import formataddr, parseaddr

# 封装一个格式化地址的函数(避免重复写)
def _format_addr(name, addr):
    # parseaddr可以解析类似 "张三 <zhangsan@example.com>" 的格式,这里我们也可以直接传
    return formataddr((Header(name, 'utf-8').encode(), addr))

# 设置From、To、Subject
msg['From'] = _format_addr('Python小助手', from_addr)
msg['To'] = _format_addr('亲爱的测试员', to_addr)
msg['Subject'] = Header('Python发来的第一封正经邮件', 'utf-8').encode()

4. 进阶场景:各种邮件类型

4.1 HTML邮件

只需要把 MIMEText 的第二个参数改成 'html'

html_body = """
<html>
<body>
    <h1 style="color: #00BFFF;">Hello 进阶版!</h1>
    <p>这是一封带<b>加粗</b>、<i>斜体</i>、还有<a href="https://www.python.org">链接</a>的HTML邮件</p>
</body>
</html>
"""

msg = MIMEText(html_body, 'html', 'utf-8')
# 记得设置邮件头!

4.2 带附件的邮件

需要用 MIMEMultipart 创建一个“容器”,先放正文,再把附件“挂”上去:

from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders

# 创建容器(默认类型mixed,混合正文和附件)
msg = MIMEMultipart()
# 设置邮件头(同上,略)

# 先加正文
msg.attach(MIMEText('附件里是本周工作总结,请查收😊', 'plain', 'utf-8'))

# 再加附件(以PDF为例,图片同理)
with open('weekly_report.pdf', 'rb') as f:
    # 创建MIMEBase对象,参数依次是:主类型、子类型、文件名
    attachment = MIMEBase('application', 'octet-stream', filename='weekly_report.pdf')
    # 设置Content-Disposition,告诉客户端这是附件
    attachment.add_header('Content-Disposition', 'attachment', filename='weekly_report.pdf')
    # 读取文件内容并编码(邮件传输必须用Base64)
    attachment.set_payload(f.read())
    encoders.encode_base64(attachment)
    # 挂到容器上
    msg.attach(attachment)

4.3 HTML中嵌入本地图片

普通HTML用的是网络图片链接,本地图片需要用 Content-ID(cid)绑定:

msg = MIMEMultipart()
# 设置邮件头(略)

# 写带cid引用的HTML
html_with_img = """
<html>
<body>
    <h1>这是一张本地图片</h1>
    <p><img src="cid:cat_pic" width="300"></p>
</body>
</html>
"""
msg.attach(MIMEText(html_with_img, 'html', 'utf-8'))

# 添加图片并设置cid
with open('cat.jpg', 'rb') as f:
    # 也可以用MIMEImage,比MIMEBase更简单,不用手动设置主/子类型
    img = MIMEImage(f.read())
    img.add_header('Content-ID', '<cat_pic>')  # 注意这里的尖括号!
    msg.attach(img)

4.4 同时支持HTML和纯文本

有些邮箱客户端不支持HTML(或者用户关闭了自动加载HTML),需要用 MIMEMultipart('alternative')纯文本在前,HTML在后,客户端会自动选能显示的:

msg = MIMEMultipart('alternative')
# 设置邮件头(略)

# 纯文本版本
msg.attach(MIMEText('这是纯文本备用内容,请使用支持HTML的客户端查看。', 'plain', 'utf-8'))
# HTML版本
msg.attach(MIMEText(html_body, 'html', 'utf-8'))

5. 安全优先:加密SMTP连接

默认的25端口是明文传输,容易被截获授权码和内容,现在几乎所有邮箱都强制要求加密,有两种方式:

5.1 STARTTLS加密

先用明文连接,握手成功后升级为TLS加密,常用端口587:

with smtplib.SMTP(smtp_server, 587) as server:
    server.starttls()  # 关键步骤:开启TLS
    server.login(from_addr, auth_code)
    server.sendmail(from_addr, [to_addr], msg.as_string())

5.2 SSL/TLS直连

一开始就建立加密连接,常用端口465:

with smtplib.SMTP_SSL(smtp_server, 465) as server:
    server.login(from_addr, auth_code)
    server.sendmail(from_addr, [to_addr], msg.as_string())

6. 避坑指南与最佳实践

  1. 不要硬编码敏感信息:授权码、邮箱可以用环境变量、配置文件(.env/.ini)或密钥管理服务
  2. 异常处理不能少:网络波动、认证失败、附件过大都会导致报错
  3. 注意编码:所有邮件内容、头部都用utf-8,避免乱码
  4. 收件人是列表sendmail 的第二个参数必须是可迭代对象(比如列表、元组),即使只有一个收件人
  5. 控制附件大小:大部分个人/免费邮箱限制在10-50MB,大文件建议传云盘发链接

带异常处理的完整示例

import os
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.header import Header
from email.utils import formataddr

def _format_addr(name, addr):
    return formataddr((Header(name, 'utf-8').encode(), addr))

def send_secure_email():
    # 从环境变量读取配置(需先在终端设置export)
    from_addr = os.getenv('SMTP_FROM')
    auth_code = os.getenv('SMTP_AUTH')
    to_addrs = os.getenv('SMTP_TO').split(',')  # 支持多个收件人,逗号分隔
    smtp_server = os.getenv('SMTP_SERVER')
    smtp_port = int(os.getenv('SMTP_PORT', 587))

    # 创建多格式邮件
    msg = MIMEMultipart('alternative')
    msg['From'] = _format_addr('自动化告警系统', from_addr)
    msg['To'] = ','.join([_format_addr('', addr) for addr in to_addrs])  # 多个收件人用逗号拼接
    msg['Subject'] = Header('服务器CPU使用率告警', 'utf-8').encode()

    # 正文
    text = "服务器CPU使用率已超过80%,请及时处理!"
    html = """
    <html>
        <body style="font-family: Arial, sans-serif;">
            <h2 style="color: #DC143C;">⚠️ 服务器告警</h2>
            <p>当前CPU使用率:<b>85%</b></p>
            <p>请登录服务器查看详情</p>
        </body>
    </html>
    """
    msg.attach(MIMEText(text, 'plain', 'utf-8'))
    msg.attach(MIMEText(html, 'html', 'utf-8'))

    # 发送
    try:
        with smtplib.SMTP(smtp_server, smtp_port) as server:
            server.starttls()
            server.login(from_addr, auth_code)
            server.sendmail(from_addr, to_addrs, msg.as_string())
        print("✅ 邮件发送成功!")
    except smtplib.SMTPAuthenticationError:
        print("❌ 认证失败,请检查邮箱和授权码")
    except smtplib.SMTPConnectError:
        print("❌ 连接失败,请检查SMTP服务器地址和端口")
    except Exception as e:
        print(f"❌ 发送出错:{e}")

if __name__ == '__main__':
    send_secure_email()

7. 简单梳理:email模块的对象关系

了解这个可以帮你更好地选择组装方式:

Message(基类,一般不直接用)
└─ MIMEBase
   ├─ MIMEMultipart(混合容器)
   └─ MIMENonMultipart(非混合容器,单一内容)
      ├─ MIMEText(文本/HTML)
      ├─ MIMEImage(图片)
      ├─ MIMEAudio(音频)
      └─ ...(其他格式)

通过这篇教程,你应该可以快速实现从简单到复杂的邮件发送功能了!根据实际需求选择合适的容器、加密方式,再加上异常处理,就能写出稳定可用的脚本。