Python发送邮件完全指南:从基础到实战

发布于:2025-08-11 ⋅ 阅读:(11) ⋅ 点赞:(0)

在日常开发中,发送邮件是一个常见的需求,比如用户注册验证、密码重置、系统通知等场景,Python提供了强大的标准库来处理邮件发送。

一、SMTP协议详解

SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)是用于发送电子邮件的标准协议。

基本概念

SMTP 是 TCP/IP 协议族的一部分,专门负责:

  • 发送邮件:从邮件客户端发送到邮件服务器,或在邮件服务器之间传输
  • 路由邮件:确定邮件的传输路径
  • 交付邮件:将邮件送达目标邮件服务器

工作原理

发送方客户端 → SMTP服务器A → SMTP服务器B → 接收方客户端

邮件传输流程:

  1. 连接建立:客户端连接到 SMTP 服务器
  2. 身份验证:验证发件人身份
  3. 邮件传输:发送邮件内容
  4. 服务器转发:SMTP 服务器将邮件转发到目标服务器
  5. 邮件存储:目标服务器存储邮件供接收方获取

安全机制

  1. SSL/TLS 加密

    • SSL:在连接建立时就加密
    • TLS:在连接建立后升级为加密连接
  2. 身份验证

    • 用户名/密码验证
    • OAuth2 验证(更安全)

二、开启 QQ 邮箱的 SMTP 服务

常用邮箱SMTP配置

# 常用邮箱SMTP配置
EMAIL_CONFIGS = {
    "gmail": {
        "smtp_server": "smtp.gmail.com",
        "smtp_port": 587
    },
    "qq": {
        "smtp_server": "smtp.qq.com",
        "smtp_port": 587
    },
    "163": {
        "smtp_server": "smtp.163.com",
        "smtp_port": 25
    },
    "126": {
        "smtp_server": "smtp.126.com",
        "smtp_port": 25
    },
    "outlook": {
        "smtp_server": "smtp-mail.outlook.com",
        "smtp_port": 587
    }
}

网页地址

qq邮箱:https://mail.qq.com/
163邮箱:https://mail.163.com/

其中qq邮箱和163邮箱在日常生活中比较常用,这里以qq邮箱为例。

开启 QQ 邮箱的 SMTP 服务

  • 登录 QQ 邮箱;
  • 进入“设置” > “账户”;
  • 在“POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务”中找到“开启服务”;
  • 开启 IMAP/SMTP服务
  • 根据提示生成 授权码(不是QQ密码,而是用于第三方登录的专用密码)。

⚠️ 注意:SMTP 服务默认关闭,必须手动开启,并获取授权码。

其他的邮箱方法也类似。

SMTP服务类型

163邮箱有IMAP/SMTP服务和POP3/SMTP服务两种类型,IMAP/SMTP服务和POP3/SMTP服务的区别在于,IMAP支持多设备同步和服务器存储,而POP3更适合本地存储和单一设备使用
主要区别:
(1)协议功能不同
IMAP(Internet Message Access Protocol)和POP3(Post Office Protocol version 3)都是用于接收邮件的协议,而SMTP(Simple Mail Transfer Protocol)则是用于发送邮件的协议。因此,IMAP/SMTP和POP3/SMTP的区别主要在于接收邮件部分。
(2)邮件存储方式不同
IMAP支持将邮件存储在远程服务器上,用户可以通过多个设备同步查看邮件内容,而不会将邮件从服务器删除。这种方式适合需要跨设备访问邮件的用户。
POP3则通常会将邮件从服务器下载到本地设备,并在下载后可选择是否从服务器删除邮件。因此,POP3更适合邮件主要在单一设备上使用的场景。
(3)适用场景不同
IMAP适合需要多设备同步、灵活管理邮件的用户,例如在手机、电脑和平板之间切换使用邮箱的场景。POP3则更适合希望将邮件集中存储在本地设备、节省服务器空间的用户,例如仅在电脑上查看邮件的场景。


三、代码实现

得益于电子邮件系统的互通性,不同服务提供商的邮箱之间可以互相通信,也就是说可以用163邮箱发送邮件给qq邮箱,下面示例用163邮箱发送邮件给qq邮箱。

import datetime
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header



def send_email_task(subject, content):
    """
    发送邮件的任务函数

    Args:
        subject: 邮件主题
        content: 邮件内容
    """
    # 邮件配置信息(请根据实际情况修改)
    smtp_server = "smtp.163.com"    # SMTP服务器地址
    smtp_port = 25                  # SMTP端口
    sender_email = "xxx@163.com"    # 发件人邮箱
    sender_password = "xxx"         # 发件人邮箱授权码
    receiver_email = "xxx@qq.com"   # 收件人邮箱地址

    try:
        # 创建邮件对象
        message = MIMEMultipart()
        message["From"] = Header("邮件系统", 'utf-8')      # 设置邮件发送者
        message["To"] = Header(receiver_email, 'utf-8')   # 设置邮件接受者
        message["Subject"] = Header(subject, 'utf-8')     # 设置邮件主题

        # 添加邮件内容
        message.attach(MIMEText(content, 'plain', 'utf-8'))

        # 连接SMTP服务器并发送邮件
        server = smtplib.SMTP(smtp_server, smtp_port)
        server.starttls()  # 启用TLS加密
        server.login(sender_email, sender_password)
        server.sendmail(sender_email, receiver_email, message.as_string())
        server.quit()

        print(f"[邮件发送] 邮件已成功发送至 {receiver_email},时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    except Exception as e:
        print(f"[邮件发送] 邮件发送失败: {str(e)}")


if __name__ == "__main__":
    email_content = f"这是一封邮件,发送时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
    send_email_task("邮件提醒", email_content)

TLS 加密详解

代码中server.starttls()可能存在疑问,下面做下解析。

TLS(Transport Layer Security,传输层安全协议)是一种加密通信协议,用于在网络通信中提供安全性和数据完整性。

TLS 是 SSL(Secure Sockets Layer)的继任者,主要用于:

  • 加密数据传输:防止数据被窃听
  • 身份验证:确认通信双方的身份
  • 数据完整性:确保数据在传输过程中未被篡改

改用SSL加密协议:

server = smtplib.SMTP_SSL(mail_host, 465)

'plain' 的作用

在代码 message.attach(MIMEText(content, 'plain', 'utf-8')) 中,'plain' 参数指定了邮件正文的 MIME 类型。

'plain' 表示邮件正文内容是纯文本格式(Plain Text),它有以下特点:
1. 纯文本特性

  • 不支持任何格式化(如粗体、斜体、颜色等)
  • 不支持图片、链接样式等复杂元素
  • 所有内容都以普通文本形式显示

2. 兼容性

  • 几乎所有邮件客户端都支持纯文本邮件
  • 占用空间小,传输速度快
  • 不会被邮件客户端误判为垃圾邮件

改用HTML 格式:

# HTML格式(html)
html_content = """
<html>
  <body>
    <h2>HTML邮件</h2>
    <p>这是一封<b>HTML格式</b>的邮件</p>
  </body>
</html>
"""
message.attach(MIMEText(html_content, 'html', 'utf-8'))

纯文本显示:

  这是一封纯文本邮件
  欢迎使用

HTML显示:

  HTML邮件
  
  这是一封**HTML格式**的邮件

何时使用 'plain'

  • 发送简单通知或提醒
  • 需要确保邮件在所有设备上都能正常显示
  • 发送代码、日志等技术内容
  • 对邮件格式要求不高

何时使用 'html'

  • 需要丰富的文本格式
  • 包含链接、图片等元素
  • 制作精美的邮件模板
  • 需要更好的视觉效果

完整示例

# 纯文本邮件
message.attach(MIMEText("这是一封纯文本邮件\n欢迎使用", 'plain', 'utf-8'))

# HTML邮件
message.attach(MIMEText("<h1>HTML邮件</h1><p>欢迎使用</p>", 'html', 'utf-8'))

# 同时支持纯文本和HTML(推荐)
text_part = MIMEText("这是一封纯文本邮件", 'plain', 'utf-8')
html_part = MIMEText("<h1>HTML邮件</h1><p>欢迎使用</p>", 'html', 'utf-8')
message.attach(text_part)
message.attach(html_part)

问题处理

上面的方式处理,代码执行可能存在From 标头格式不符合标准的问题。优化后代码:

import datetime
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.utils import formataddr


# 常用邮箱SMTP配置
EMAIL_CONFIGS = {
    "gmail": {
        "smtp_server": "smtp.gmail.com",
        "smtp_port": 587
    },
    "qq": {
        "smtp_server": "smtp.qq.com",
        "smtp_port": 587
    },
    "163": {
        "smtp_server": "smtp.163.com",
        "smtp_port": 25
    },
    "126": {
        "smtp_server": "smtp.126.com",
        "smtp_port": 25
    },
    "outlook": {
        "smtp_server": "smtp-mail.outlook.com",
        "smtp_port": 587
    }
}


def send_email_task(subject, content):
    """
    发送邮件的任务函数

    Args:
        subject: 邮件主题
        content: 邮件内容
    """
    # 邮件配置信息(请根据实际情况修改)
    smtp_server = EMAIL_CONFIGS["qq"]["smtp_server"]  # SMTP服务器地址
    smtp_port = EMAIL_CONFIGS["qq"]["smtp_port"]  # SMTP端口

    sender_email = "xxx@qq.com"  # 发件人邮箱
    sender_password = "xxx"  # 发件人邮箱授权码
    receiver_email = "xxx@163.com"  # 收件人邮箱地址

    try:
        # 创建邮件对象
        message = MIMEMultipart()
        message['From'] = formataddr(("邮件系统", sender_email))	# 设置邮件发送者
        message["To"] = Header(receiver_email, 'utf-8')   # 设置邮件接受者
        message["Subject"] = Header(subject, 'utf-8')     # 设置邮件主题

        # 添加邮件内容
        message.attach(MIMEText(content, 'plain', 'utf-8'))

        # 连接SMTP服务器并发送邮件
        server = smtplib.SMTP(smtp_server, smtp_port)
        # server.set_debuglevel(1)  # 启用调试模式,显示详细通信过程
        server.starttls()  # 启用TLS加密
        server.login(sender_email, sender_password)
        server.sendmail(sender_email, receiver_email, message.as_string())
        server.quit()

        print(f"[邮件发送] 邮件已成功发送至 {receiver_email},时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    except Exception as e:
        print(f"[邮件发送] 邮件发送失败: {str(e)}")


if __name__ == "__main__":
    email_content = f"这是一封邮件,发送时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
    send_email_task("邮件提醒", email_content)

(1)解决From 标头格式问题

完整的 From 标头格式应该是 昵称编码部分 + 空格 + <邮箱地址>。之前的From 标头只有编码后的昵称部分,缺少了实际的 <邮箱地址> 部分

不只设置昵称,要同时设置昵称和邮箱地址。确保邮件的 From 标头包含完整的格式:昵称 <邮箱地址>,即使昵称部分是编码的。


# message["From"] = Header("邮件系统", 'utf-8')

message['From'] = formataddr(("邮件系统", sender_email))

(2)增加调试模式

另外,打开这段注释的代码,可以改成调试模式,增加更过日志输出。

# server.set_debuglevel(1)  # 启用调试模式,显示详细通信过程

(3)SMTP服务器地址及端口改成可选的常量

常用邮箱SMTP配置,放入EMAIL_CONFIGS常量中供自行选择,便于替换

定时任务版本

使用简单的threading.Timer定时器来实现定时发送邮件:

import threading
import datetime
import time
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.utils import formataddr


# 常用邮箱SMTP配置
EMAIL_CONFIGS = {
    "gmail": {
        "smtp_server": "smtp.gmail.com",
        "smtp_port": 587
    },
    "qq": {
        "smtp_server": "smtp.qq.com",
        "smtp_port": 587
    },
    "163": {
        "smtp_server": "smtp.163.com",
        "smtp_port": 25
    },
    "126": {
        "smtp_server": "smtp.126.com",
        "smtp_port": 25
    },
    "outlook": {
        "smtp_server": "smtp-mail.outlook.com",
        "smtp_port": 587
    }
}


class SimpleTimer:
    """
    简单的定时任务工具类,基于 threading.Timer 实现
    使用实例方法
    """

    def __init__(self):
        self.running_tasks = {}  # 用于跟踪正在运行的任务

    def _run_task(self, func, interval, task_key, args, kwargs):
        """
        内部运行任务函数

        Args:
            func: 要执行的函数
            interval: 执行间隔(秒)
            task_key: 任务唯一标识
            args: 传递给函数的位置参数
            kwargs: 传递给函数的关键字参数
        """
        if not self.running_tasks.get(task_key, False):
            return
        func(*args, **kwargs)

        # 重新启动定时器实现循环执行(仅当任务仍在运行时)
        if self.running_tasks.get(task_key, False):
            timer = threading.Timer(interval, self._run_task, [func, interval, task_key, args, kwargs])
            timer.start()

            # 更新任务对应的timer,以便清理引用
            self.running_tasks[task_key] = timer

    def start_task(self, func, interval, *args, **kwargs):
        """
        启动定时任务

        Args:
            func: 要执行的函数
            interval: 执行间隔(秒)
            *args: 传递给函数的位置参数
            **kwargs: 传递给函数的关键字参数

        Returns:
            task_key: 任务标识符,可用于停止任务
        """
        # 生成任务唯一标识
        task_key = (id(func), args, tuple(sorted(kwargs.items())))

        timer = threading.Timer(interval, self._run_task, [func, interval, task_key, args, kwargs])
        self.running_tasks[task_key] = timer
        timer.start()
        print(f"定时任务已启动,将在 {interval} 秒后执行")

        return task_key

    def stop_task(self, task_key):
        """
        停止指定的定时任务并清理引用

        Args:
            task_key: 要停止的任务标识符
        """
        if task_key in self.running_tasks:
            timer = self.running_tasks[task_key]
            if timer:
                timer.cancel()
                # 清理引用以帮助垃圾回收
                timer.function = None
                timer.args = None
                timer.kwargs = None

            # 标记任务为停止状态
            self.running_tasks[task_key] = False
            print("定时任务已停止")

    def stop_all_tasks(self):
        """
        停止所有定时任务
        """
        for task_key in list(self.running_tasks.keys()):
            self.stop_task(task_key)
        self.running_tasks.clear()
        print("所有定时任务已停止")


def send_email_task(subject, content):
    """
    发送邮件的任务函数

    Args:
        subject: 邮件主题
        content: 邮件内容
    """
    # 邮件配置信息(请根据实际情况修改)
    smtp_server = EMAIL_CONFIGS["163"]["smtp_server"]  # SMTP服务器地址
    smtp_port = EMAIL_CONFIGS["163"]["smtp_port"]  # SMTP端口
    sender_email = "xxx@qq.com"  # 发件人邮箱
    sender_password = "xxx"  # 发件人邮箱授权码
    receiver_email = "xxx@163.com"  # 收件人邮箱地址

    try:
        # 创建邮件对象
        message = MIMEMultipart()
        message['From'] = formataddr(("定时邮件系统", sender_email))	 # 设置邮件发送者
        message["To"] = Header(receiver_email, 'utf-8')   # 设置邮件接受者
        message["Subject"] = Header(subject, 'utf-8')     # 设置邮件主题

        # 添加邮件内容
        message.attach(MIMEText(content, 'plain', 'utf-8'))

        # 连接SMTP服务器并发送邮件
        server = smtplib.SMTP(smtp_server, smtp_port)
        # server.set_debuglevel(1)  # 启用调试模式,显示详细通信过程
        server.starttls()  # 启用TLS加密
        server.login(sender_email, sender_password)
        server.sendmail(sender_email, receiver_email, message.as_string())
        server.quit()

        print(f"[邮件发送] 邮件已成功发送至 {receiver_email},时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    except Exception as e:
        print(f"[邮件发送] 邮件发送失败: {str(e)}")


def main():
    """
    使用 SimpleTimer 工具类的示例
    """
    print("简单定时任务工具类示例启动...")

    # 创建定时器实例
    timer_scheduler = SimpleTimer()

    # 启动定时邮件发送任务,每2分钟发送一次
    email_content = f"这是一封定时邮件,发送时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
    task_key = timer_scheduler.start_task(
        send_email_task,
        5,  # 2分钟 = 120秒
        "定时邮件提醒",
        email_content
    )

    # 主程序可以继续做其他事情
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("程序退出")
        # 停止所有定时任务
        timer_scheduler.stop_all_tasks()
        print("所有定时任务已停止,程序结束")


if __name__ == "__main__":
    main()


网站公告

今日签到

点亮在社区的每一天
去签到