SMTP(Simple Mail Transfer Protocol)是用于发送电子邮件的协议。Python 的 smtplib 模块提供了发送邮件的功能,email 模块用于构建邮件内容。

安装与基本导入

SMTP 模块是 Python 标准库的一部分,无需额外安装:

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from email.header import Header
from email.utils import formataddr
import os

基本邮件发送

1. 发送纯文本邮件

def send_simple_email():
    # 邮件服务器配置
    smtp_server = "smtp.163.com"  # 以163邮箱为例
    smtp_port = 25                # 端口:25(普通), 465(SSL), 587(TLS)
    sender_email = "your_email@163.com"
    password = "your_password"    # 如果是163邮箱,使用授权码而非登录密码
    
    # 收件人
    receiver_email = "receiver@example.com"
    
    # 创建邮件内容
    subject = "测试邮件主题"
    body = "这是一封测试邮件的正文内容。"
    
    # 构建邮件
    message = MIMEText(body, 'plain', 'utf-8')
    message['From'] = formataddr(("发件人名称", sender_email))
    message['To'] = formataddr(("收件人名称", receiver_email))
    message['Subject'] = Header(subject, 'utf-8')
    
    try:
        # 连接服务器并发送
        with smtplib.SMTP(smtp_server, smtp_port) as server:
            server.login(sender_email, password)
            server.sendmail(sender_email, receiver_email, message.as_string())
        print("邮件发送成功!")
    except Exception as e:
        print(f"邮件发送失败: {e}")

# send_simple_email()

2. 不同加密方式连接

def connect_with_different_encryption():
    smtp_server = "smtp.163.com"
    sender_email = "your_email@163.com"
    password = "your_password"
    
    # 方式1: 普通连接(不推荐,可能被拦截)
    try:
        server = smtplib.SMTP(smtp_server, 25)
        server.login(sender_email, password)
        print("普通连接成功")
        server.quit()
    except Exception as e:
        print(f"普通连接失败: {e}")
    
    # 方式2: SSL加密连接
    try:
        server = smtplib.SMTP_SSL(smtp_server, 465)
        server.login(sender_email, password)
        print("SSL连接成功")
        server.quit()
    except Exception as e:
        print(f"SSL连接失败: {e}")
    
    # 方式3: STARTTLS加密连接
    try:
        server = smtplib.SMTP(smtp_server, 587)
        server.starttls()  # 启用加密
        server.login(sender_email, password)
        print("STARTTLS连接成功")
        server.quit()
    except Exception as e:
        print(f"STARTTLS连接失败: {e}")

复杂邮件发送

1. 发送HTML格式邮件

def send_html_email():
    smtp_server = "smtp.163.com"
    smtp_port = 465
    sender_email = "your_email@163.com"
    password = "your_password"
    receiver_email = "receiver@example.com"
    
    # HTML内容
    subject = "HTML格式测试邮件"
    html_body = """
    <html>
        <body>
            <h1 style="color: #ff6600;">这是一封HTML邮件</h1>
            <p>欢迎使用<strong>Python SMTP</strong>模块发送邮件!</p>
            <ul>
                <li>列表项1</li>
                <li>列表项2</li>
                <li>列表项3</li>
            </ul>
            <p><a href="https://www.python.org">访问Python官网</a></p>
        </body>
    </html>
    """
    
    # 创建HTML邮件
    message = MIMEText(html_body, 'html', 'utf-8')
    message['From'] = formataddr(("发件人", sender_email))
    message['To'] = formataddr(("收件人", receiver_email))
    message['Subject'] = Header(subject, 'utf-8')
    
    try:
        with smtplib.SMTP_SSL(smtp_server, smtp_port) as server:
            server.login(sender_email, password)
            server.sendmail(sender_email, receiver_email, message.as_string())
        print("HTML邮件发送成功!")
    except Exception as e:
        print(f"HTML邮件发送失败: {e}")

2. 发送带附件的邮件

def send_email_with_attachment():
    smtp_server = "smtp.163.com"
    smtp_port = 465
    sender_email = "your_email@163.com"
    password = "your_password"
    receiver_email = "receiver@example.com"
    
    # 创建多部分邮件
    message = MIMEMultipart()
    message['From'] = formataddr(("发件人", sender_email))
    message['To'] = formataddr(("收件人", receiver_email))
    message['Subject'] = Header("带附件的测试邮件", 'utf-8')
    
    # 邮件正文
    text_body = """
    您好!
    
    这是一封带有附件的测试邮件。
    请查收附件中的文件。
    
    祝好!
    Python发件程序
    """
    
    # 添加文本部分
    text_part = MIMEText(text_body, 'plain', 'utf-8')
    message.attach(text_part)
    
    # 添加附件1:文本文件
    attachment1 = MIMEApplication(open('example.txt', 'rb').read())
    attachment1.add_header('Content-Disposition', 'attachment', 
                          filename=Header('示例文件.txt', 'utf-8').encode())
    message.attach(attachment1)
    
    # 添加附件2:图片文件
    try:
        with open('image.jpg', 'rb') as f:
            attachment2 = MIMEApplication(f.read())
            attachment2.add_header('Content-Disposition', 'attachment',
                                 filename=Header('图片.jpg', 'utf-8').encode())
            message.attach(attachment2)
    except FileNotFoundError:
        print("图片文件未找到,跳过添加图片附件")
    
    try:
        with smtplib.SMTP_SSL(smtp_server, smtp_port) as server:
            server.login(sender_email, password)
            server.sendmail(sender_email, receiver_email, message.as_string())
        print("带附件的邮件发送成功!")
    except Exception as e:
        print(f"邮件发送失败: {e}")

3. 发送混合内容邮件(文本+HTML)

def send_mixed_email():
    smtp_server = "smtp.163.com"
    smtp_port = 465
    sender_email = "your_email@163.com"
    password = "your_password"
    receiver_email = "receiver@example.com"
    
    # 创建多部分邮件(alternative类型)
    message = MIMEMultipart('alternative')
    message['From'] = formataddr(("发件人", sender_email))
    message['To'] = formataddr(("收件人", receiver_email))
    message['Subject'] = Header("混合内容测试邮件", 'utf-8')
    
    # 纯文本版本(用于不支持HTML的邮件客户端)
    text_body = """
    这是一封测试邮件。
    如果您看到这条消息,说明您的邮件客户端不支持HTML格式。
    请使用支持HTML的邮件客户端查看完整内容。
    """
    
    # HTML版本
    html_body = """
    <html>
        <body>
            <h1 style="color: #3366cc;">欢迎!</h1>
            <p>这是一封<strong>HTML格式</strong>的测试邮件。</p>
            <p>如果您的邮件客户端支持HTML,您将看到格式化的内容。</p>
            <table border="1" style="border-collapse: collapse;">
                <tr>
                    <th>项目</th>
                    <th>值</th>
                </tr>
                <tr>
                    <td>名称</td>
                    <td>测试数据</td>
                </tr>
            </table>
        </body>
    </html>
    """
    
    # 添加两个版本(邮件客户端会选择支持的格式)
    text_part = MIMEText(text_body, 'plain', 'utf-8')
    html_part = MIMEText(html_body, 'html', 'utf-8')
    
    message.attach(text_part)
    message.attach(html_part)
    
    try:
        with smtplib.SMTP_SSL(smtp_server, smtp_port) as server:
            server.login(sender_email, password)
            server.sendmail(sender_email, receiver_email, message.as_string())
        print("混合内容邮件发送成功!")
    except Exception as e:
        print(f"邮件发送失败: {e}")

高级功能

1. 批量发送邮件

def send_batch_emails():
    smtp_server = "smtp.163.com"
    smtp_port = 465
    sender_email = "your_email@163.com"
    password = "your_password"
    
    # 收件人列表
    receivers = [
        {"name": "张三", "email": "zhangsan@example.com"},
        {"name": "李四", "email": "lisi@example.com"},
        {"name": "王五", "email": "wangwu@example.com"}
    ]
    
    try:
        with smtplib.SMTP_SSL(smtp_server, smtp_port) as server:
            server.login(sender_email, password)
            
            for receiver in receivers:
                # 为每个收件人个性化邮件
                subject = f"亲爱的{receiver['name']},这是一封个性化邮件"
                body = f"""
                尊敬的{receiver['name']}:
                
                这是一封个性化测试邮件。
                感谢您使用我们的服务!
                
                祝好!
                Python发件系统
                """
                
                message = MIMEText(body, 'plain', 'utf-8')
                message['From'] = formataddr(("系统发件人", sender_email))
                message['To'] = formataddr((receiver['name'], receiver['email']))
                message['Subject'] = Header(subject, 'utf-8')
                
                server.sendmail(sender_email, receiver['email'], message.as_string())
                print(f"发送给 {receiver['name']} 的邮件成功!")
                
                # 避免发送过快被服务器限制
                import time
                time.sleep(1)
                
    except Exception as e:
        print(f"批量发送失败: {e}")

2. 邮件发送类封装

class EmailSender:
    def __init__(self, smtp_server, smtp_port, sender_email, password, use_ssl=True):
        self.smtp_server = smtp_server
        self.smtp_port = smtp_port
        self.sender_email = sender_email
        self.password = password
        self.use_ssl = use_ssl
        
    def connect(self):
        """连接邮件服务器"""
        try:
            if self.use_ssl:
                self.server = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port)
            else:
                self.server = smtplib.SMTP(self.smtp_server, self.smtp_port)
                self.server.starttls()  # 启用TLS加密
            
            self.server.login(self.sender_email, self.password)
            return True
        except Exception as e:
            print(f"连接失败: {e}")
            return False
    
    def send_email(self, to_email, to_name, subject, body, 
                  html_body=None, attachments=None):
        """发送邮件"""
        try:
            # 创建邮件
            if html_body or attachments:
                message = MIMEMultipart()
                if html_body:
                    message = MIMEMultipart('alternative')
            else:
                message = MIMEText(body, 'plain', 'utf-8')
            
            message['From'] = formataddr(("系统发件人", self.sender_email))
            message['To'] = formataddr((to_name, to_email))
            message['Subject'] = Header(subject, 'utf-8')
            
            # 添加正文
            if html_body:
                text_part = MIMEText(body, 'plain', 'utf-8')
                html_part = MIMEText(html_body, 'html', 'utf-8')
                message.attach(text_part)
                message.attach(html_part)
            elif attachments:
                text_part = MIMEText(body, 'plain', 'utf-8')
                message.attach(text_part)
            
            # 添加附件
            if attachments:
                for attachment_path in attachments:
                    if os.path.exists(attachment_path):
                        with open(attachment_path, 'rb') as f:
                            attachment = MIMEApplication(f.read())
                            filename = os.path.basename(attachment_path)
                            attachment.add_header('Content-Disposition', 'attachment',
                                                filename=Header(filename, 'utf-8').encode())
                            message.attach(attachment)
            
            # 发送邮件
            self.server.sendmail(self.sender_email, to_email, message.as_string())
            return True
            
        except Exception as e:
            print(f"发送失败: {e}")
            return False
    
    def disconnect(self):
        """断开连接"""
        if hasattr(self, 'server'):
            self.server.quit()

# 使用示例
def use_email_sender():
    # 创建发送器实例
    sender = EmailSender(
        smtp_server="smtp.163.com",
        smtp_port=465,
        sender_email="your_email@163.com",
        password="your_password",
        use_ssl=True
    )
    
    # 连接服务器
    if sender.connect():
        # 发送简单邮件
        sender.send_email(
            to_email="receiver@example.com",
            to_name="收件人",
            subject="测试邮件",
            body="这是一封测试邮件。"
        )
        
        # 发送带HTML的邮件
        sender.send_email(
            to_email="receiver@example.com",
            to_name="收件人",
            subject="HTML测试邮件",
            body="纯文本内容",
            html_body="<h1>HTML内容</h1><p>这是HTML版本</p>"
        )
        
        # 断开连接
        sender.disconnect()

3. 常见邮箱服务器配置

# 常见邮箱服务器配置
EMAIL_CONFIGS = {
    '163': {
        'smtp_server': 'smtp.163.com',
        'smtp_port': 465,
        'use_ssl': True
    },
    'qq': {
        'smtp_server': 'smtp.qq.com',
        'smtp_port': 465,
        'use_ssl': True
    },
    'gmail': {
        'smtp_server': 'smtp.gmail.com',
        'smtp_port': 587,  # Gmail 使用 STARTTLS
        'use_ssl': False
    },
    'outlook': {
        'smtp_server': 'smtp.office365.com',
        'smtp_port': 587,
        'use_ssl': False
    }
}

def get_email_config(email_address):
    """根据邮箱地址获取配置"""
    if '@163.com' in email_address:
        return EMAIL_CONFIGS['163']
    elif '@qq.com' in email_address:
        return EMAIL_CONFIGS['qq']
    elif '@gmail.com' in email_address:
        return EMAIL_CONFIGS['gmail']
    elif '@outlook.com' in email_address or '@hotmail.com' in email_address:
        return EMAIL_CONFIGS['outlook']
    else:
        raise ValueError("不支持的邮箱类型")

错误处理与调试

def robust_email_sending():
    smtp_server = "smtp.163.com"
    sender_email = "your_email@163.com"
    password = "your_password"
    
    try:
        # 测试连接
        server = smtplib.SMTP_SSL(smtp_server, 465)
        server.set_debuglevel(1)  # 开启调试模式,显示通信详情
        
        # 登录
        server.login(sender_email, password)
        print("登录成功")
        
        # 发送测试邮件
        message = MIMEText("测试邮件内容", 'plain', 'utf-8')
        message['From'] = sender_email
        message['To'] = "receiver@example.com"
        message['Subject'] = "测试邮件"
        
        server.sendmail(sender_email, "receiver@example.com", message.as_string())
        print("邮件发送成功")
        
        server.quit()
        
    except smtplib.SMTPAuthenticationError:
        print("认证失败:用户名或密码错误")
    except smtplib.SMTPConnectError:
        print("连接服务器失败")
    except smtplib.SMTPRecipientsRefused:
        print("收件人被拒绝")
    except smtplib.SMTPSenderRefused:
        print("发件人被拒绝")
    except smtplib.SMTPDataError:
        print("服务器拒绝数据")
    except Exception as e:
        print(f"其他错误: {e}")

安全注意事项

import getpass
from configparser import ConfigParser

def secure_email_sending():
    # 安全地获取密码(不显示在终端)
    password = getpass.getpass("请输入邮箱密码/授权码: ")
    
    # 或者从配置文件读取(推荐)
    config = ConfigParser()
    config.read('email_config.ini')
    
    smtp_server = config.get('email', 'smtp_server')
    sender_email = config.get('email', 'sender_email')
    password = config.get('email', 'password')  # 加密存储
    
    # 使用环境变量(更安全)
    import os
    password = os.getenv('EMAIL_PASSWORD')
    
    # 发送邮件...

总结

主要优点:

  • Python 标准库,无需额外安装
  • 支持多种邮件格式(文本、HTML、附件等)
  • 支持多种加密方式(SSL/TLS)
  • 灵活的邮件构建功能

常见问题解决:

  1. 认证失败:检查是否使用授权码而非登录密码
  2. 连接超时:检查网络连接和防火墙设置
  3. 被拒收:检查发件人信誉和邮件内容是否被标记为垃圾邮件

最佳实践:

  • 使用加密连接(SSL/TLS)
  • 密码/授权码安全存储
  • 添加适当的错误处理
  • 避免发送垃圾邮件
  • 测试不同的邮件客户端兼容性

SMTP 模块是 Python 发送邮件的标准解决方案,适用于各种邮件发送需求。