Veloris.
返回索引
设计实战 2026-02-14

Python邮件自动化:smtplib+email模块,定时发送报告不求人

3 分钟
686 words

Python邮件自动化:smtplib+email模块,定时发送报告不求人

邮件自动化是办公自动化的重要组成部分,可以实现定时发送报告、批量发送通知等功能。本篇将介绍如何使用Python发送邮件,包括纯文本、HTML格式和带附件的邮件。


1. 邮件协议简介

协议用途端口
SMTP发送邮件25, 465(SSL), 587(TLS)
POP3接收邮件(下载后删除)110, 995(SSL)
IMAP接收邮件(同步)143, 993(SSL)

常用邮箱SMTP服务器

邮箱SMTP服务器端口
QQ邮箱smtp.qq.com465/587
163邮箱smtp.163.com465/994
Gmailsmtp.gmail.com587
Outlooksmtp.office365.com587
企业邮箱根据企业配置-

⚠️ 注意:大多数邮箱需要开启SMTP服务并获取授权码,而不是使用登录密码。


2. 发送简单邮件

import smtplib
from email.mime.text import MIMEText
from email.header import Header

def send_simple_email(
    sender_email,
    sender_password,
    receiver_email,
    subject,
    content,
    smtp_server='smtp.qq.com',
    smtp_port=465
):
    """
    发送简单文本邮件
    
    Args:
        sender_email: 发件人邮箱
        sender_password: 授权码(不是登录密码)
        receiver_email: 收件人邮箱
        subject: 邮件主题
        content: 邮件内容
        smtp_server: SMTP服务器
        smtp_port: SMTP端口
    """
    # 创建邮件对象
    message = MIMEText(content, 'plain', 'utf-8')
    message['From'] = Header(sender_email)
    message['To'] = Header(receiver_email)
    message['Subject'] = Header(subject, 'utf-8')
    
    try:
        # 连接SMTP服务器(SSL)
        server = smtplib.SMTP_SSL(smtp_server, smtp_port)
        
        # 登录
        server.login(sender_email, sender_password)
        
        # 发送邮件
        server.sendmail(sender_email, [receiver_email], message.as_string())
        
        print("邮件发送成功!")
        
    except smtplib.SMTPException as e:
        print(f"邮件发送失败:{e}")
    
    finally:
        server.quit()

# 使用示例
# send_simple_email(
#     sender_email='[email protected]',
#     sender_password='your_auth_code',  # QQ邮箱授权码
#     receiver_email='[email protected]',
#     subject='测试邮件',
#     content='这是一封测试邮件。'
# )

3. 发送HTML邮件

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

def send_html_email(
    sender_email,
    sender_password,
    receiver_email,
    subject,
    html_content,
    smtp_server='smtp.qq.com',
    smtp_port=465
):
    """发送HTML格式邮件"""
    
    # 创建多部分邮件
    message = MIMEMultipart('alternative')
    message['From'] = sender_email
    message['To'] = receiver_email
    message['Subject'] = Header(subject, 'utf-8')
    
    # 添加HTML内容
    html_part = MIMEText(html_content, 'html', 'utf-8')
    message.attach(html_part)
    
    try:
        server = smtplib.SMTP_SSL(smtp_server, smtp_port)
        server.login(sender_email, sender_password)
        server.sendmail(sender_email, [receiver_email], message.as_string())
        print("HTML邮件发送成功!")
    except Exception as e:
        print(f"发送失败:{e}")
    finally:
        server.quit()

# HTML邮件模板
html_template = """
<!DOCTYPE html>
<html>
<head>
    <style>
        body { font-family: Arial, sans-serif; }
        .container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .header { background-color: #4472C4; color: white; padding: 20px; text-align: center; }
        .content { padding: 20px; background-color: #f5f5f5; }
        .footer { text-align: center; padding: 10px; color: #666; font-size: 12px; }
        table { width: 100%; border-collapse: collapse; margin: 20px 0; }
        th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
        th { background-color: #4472C4; color: white; }
        .highlight { color: #e74c3c; font-weight: bold; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>月度销售报告</h1>
        </div>
        <div class="content">
            <p>尊敬的领导:</p>
            <p>以下是本月销售数据汇总:</p>
            
            <table>
                <tr>
                    <th>产品</th>
                    <th>销量</th>
                    <th>金额</th>
                </tr>
                <tr>
                    <td>产品A</td>
                    <td>100</td>
                    <td>¥10,000</td>
                </tr>
                <tr>
                    <td>产品B</td>
                    <td>150</td>
                    <td>¥15,000</td>
                </tr>
                <tr>
                    <td><strong>合计</strong></td>
                    <td><strong>250</strong></td>
                    <td class="highlight">¥25,000</td>
                </tr>
            </table>
            
            <p>本月销售额同比增长 <span class="highlight">15%</span>。</p>
            <p>详细报告请查看附件。</p>
        </div>
        <div class="footer">
            <p>此邮件由系统自动发送,请勿回复。</p>
        </div>
    </div>
</body>
</html>
"""

# 使用示例
# send_html_email(
#     sender_email='[email protected]',
#     sender_password='your_auth_code',
#     receiver_email='[email protected]',
#     subject='月度销售报告',
#     html_content=html_template
# )

4. 发送带附件的邮件

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.application import MIMEApplication
from email import encoders
from pathlib import Path

def send_email_with_attachments(
    sender_email,
    sender_password,
    receiver_emails,  # 支持多个收件人
    subject,
    content,
    attachments=None,  # 附件列表
    cc_emails=None,    # 抄送
    bcc_emails=None,   # 密送
    smtp_server='smtp.qq.com',
    smtp_port=465,
    is_html=False
):
    """
    发送带附件的邮件
    
    Args:
        receiver_emails: 收件人列表
        attachments: 附件文件路径列表
        cc_emails: 抄送列表
        bcc_emails: 密送列表
    """
    # 创建邮件
    message = MIMEMultipart()
    message['From'] = sender_email
    message['To'] = ', '.join(receiver_emails) if isinstance(receiver_emails, list) else receiver_emails
    message['Subject'] = subject
    
    if cc_emails:
        message['Cc'] = ', '.join(cc_emails)
    
    # 添加正文
    content_type = 'html' if is_html else 'plain'
    message.attach(MIMEText(content, content_type, 'utf-8'))
    
    # 添加附件
    if attachments:
        for file_path in attachments:
            path = Path(file_path)
            if not path.exists():
                print(f"附件不存在:{file_path}")
                continue
            
            with open(path, 'rb') as f:
                attachment = MIMEApplication(f.read())
                attachment.add_header(
                    'Content-Disposition',
                    'attachment',
                    filename=('utf-8', '', path.name)  # 支持中文文件名
                )
                message.attach(attachment)
    
    # 收集所有收件人
    all_recipients = []
    if isinstance(receiver_emails, list):
        all_recipients.extend(receiver_emails)
    else:
        all_recipients.append(receiver_emails)
    if cc_emails:
        all_recipients.extend(cc_emails)
    if bcc_emails:
        all_recipients.extend(bcc_emails)
    
    try:
        server = smtplib.SMTP_SSL(smtp_server, smtp_port)
        server.login(sender_email, sender_password)
        server.sendmail(sender_email, all_recipients, message.as_string())
        print(f"邮件发送成功!收件人:{all_recipients}")
    except Exception as e:
        print(f"发送失败:{e}")
    finally:
        server.quit()

# 使用示例
# send_email_with_attachments(
#     sender_email='[email protected]',
#     sender_password='your_auth_code',
#     receiver_emails=['[email protected]', '[email protected]'],
#     subject='带附件的邮件',
#     content='请查看附件。',
#     attachments=['report.xlsx', 'data.pdf'],
#     cc_emails=['[email protected]']
# )

5. 使用Outlook发送邮件

如果你使用Outlook客户端,可以通过win32com直接调用Outlook发送邮件:

# 需要安装:pip install pywin32
import win32com.client as win32

def send_outlook_email(
    to_recipients,
    subject,
    body,
    attachments=None,
    cc_recipients=None,
    is_html=False
):
    """
    使用Outlook发送邮件
    
    Args:
        to_recipients: 收件人(字符串或列表)
        subject: 主题
        body: 正文
        attachments: 附件路径列表
        cc_recipients: 抄送
        is_html: 是否HTML格式
    """
    try:
        # 创建Outlook应用
        outlook = win32.Dispatch('Outlook.Application')
        mail = outlook.CreateItem(0)  # 0表示邮件
        
        # 设置收件人
        if isinstance(to_recipients, list):
            mail.To = '; '.join(to_recipients)
        else:
            mail.To = to_recipients
        
        # 设置抄送
        if cc_recipients:
            if isinstance(cc_recipients, list):
                mail.CC = '; '.join(cc_recipients)
            else:
                mail.CC = cc_recipients
        
        # 设置主题和正文
        mail.Subject = subject
        
        if is_html:
            mail.HTMLBody = body
        else:
            mail.Body = body
        
        # 添加附件
        if attachments:
            for attachment in attachments:
                mail.Attachments.Add(attachment)
        
        # 发送
        mail.Send()
        print("Outlook邮件发送成功!")
        
    except Exception as e:
        print(f"发送失败:{e}")

# 使用示例(需要在Windows上运行,且已安装Outlook)
# send_outlook_email(
#     to_recipients='[email protected]',
#     subject='测试邮件',
#     body='这是通过Outlook发送的邮件。',
#     attachments=[r'C:\path\to\file.xlsx']
# )

# 读取Outlook收件箱
def read_outlook_inbox(folder_name='收件箱', count=10):
    """读取Outlook收件箱"""
    outlook = win32.Dispatch('Outlook.Application')
    namespace = outlook.GetNamespace('MAPI')
    
    # 获取收件箱
    inbox = namespace.GetDefaultFolder(6)  # 6表示收件箱
    messages = inbox.Items
    messages.Sort('[ReceivedTime]', True)  # 按时间降序
    
    for i, message in enumerate(messages):
        if i >= count:
            break
        print(f"发件人:{message.SenderName}")
        print(f"主题:{message.Subject}")
        print(f"时间:{message.ReceivedTime}")
        print(f"正文预览:{message.Body[:100]}...")
        print("-" * 50)

# read_outlook_inbox()

6. 邮件模板

from string import Template
from datetime import datetime

# 使用string.Template
email_template = Template("""
尊敬的 $name:

您好!

您的订单 $order_id 已确认,详情如下:

订单日期:$date
商品名称:$product
数量:$quantity
金额:¥$amount

预计送达时间:$delivery_date

如有问题,请联系客服。

祝好!
$company
""")

def generate_email_content(data):
    """根据模板生成邮件内容"""
    return email_template.substitute(data)

# 使用示例
order_data = {
    'name': '张三',
    'order_id': 'ORD20241218001',
    'date': '2024-12-18',
    'product': 'Python编程书籍',
    'quantity': 2,
    'amount': '198.00',
    'delivery_date': '2024-12-20',
    'company': 'ABC公司'
}

content = generate_email_content(order_data)
print(content)

# 使用Jinja2模板(更强大)
# pip install jinja2
from jinja2 import Template as Jinja2Template

html_template = Jinja2Template("""
<!DOCTYPE html>
<html>
<body>
    <h2>订单确认</h2>
    <p>尊敬的 {{ name }}:</p>
    <p>您的订单已确认:</p>
    <table border="1">
        <tr><th>项目</th><th>内容</th></tr>
        <tr><td>订单号</td><td>{{ order_id }}</td></tr>
        <tr><td>商品</td><td>{{ product }}</td></tr>
        <tr><td>数量</td><td>{{ quantity }}</td></tr>
        <tr><td>金额</td><td>¥{{ amount }}</td></tr>
    </table>
    {% if notes %}
    <p>备注:{{ notes }}</p>
    {% endif %}
</body>
</html>
""")

html_content = html_template.render(
    name='张三',
    order_id='ORD001',
    product='书籍',
    quantity=2,
    amount='198.00',
    notes='请尽快发货'
)

7. 批量发送邮件

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import time
import csv

def batch_send_emails(
    sender_email,
    sender_password,
    recipients_data,  # 收件人数据列表
    subject_template,
    content_template,
    smtp_server='smtp.qq.com',
    smtp_port=465,
    delay=1  # 发送间隔(秒)
):
    """
    批量发送个性化邮件
    
    Args:
        recipients_data: 收件人数据列表,每项是字典
        subject_template: 主题模板
        content_template: 内容模板
        delay: 发送间隔,避免被识别为垃圾邮件
    """
    from string import Template
    
    subject_tpl = Template(subject_template)
    content_tpl = Template(content_template)
    
    # 连接服务器
    server = smtplib.SMTP_SSL(smtp_server, smtp_port)
    server.login(sender_email, sender_password)
    
    success_count = 0
    fail_count = 0
    
    for i, recipient in enumerate(recipients_data):
        try:
            # 生成个性化内容
            subject = subject_tpl.safe_substitute(recipient)
            content = content_tpl.safe_substitute(recipient)
            
            # 创建邮件
            message = MIMEText(content, 'plain', 'utf-8')
            message['From'] = sender_email
            message['To'] = recipient['email']
            message['Subject'] = subject
            
            # 发送
            server.sendmail(sender_email, [recipient['email']], message.as_string())
            
            success_count += 1
            print(f"[{i+1}/{len(recipients_data)}] 发送成功:{recipient['email']}")
            
            # 延迟
            if delay > 0 and i < len(recipients_data) - 1:
                time.sleep(delay)
                
        except Exception as e:
            fail_count += 1
            print(f"[{i+1}/{len(recipients_data)}] 发送失败:{recipient['email']} - {e}")
    
    server.quit()
    
    print(f"\n发送完成!成功:{success_count},失败:{fail_count}")

# 从CSV读取收件人
def load_recipients_from_csv(csv_file):
    """从CSV文件加载收件人数据"""
    recipients = []
    with open(csv_file, 'r', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        for row in reader:
            recipients.append(row)
    return recipients

# 使用示例
# recipients = [
#     {'email': '[email protected]', 'name': '张三', 'product': '产品A'},
#     {'email': '[email protected]', 'name': '李四', 'product': '产品B'},
# ]
# 
# batch_send_emails(
#     sender_email='[email protected]',
#     sender_password='your_auth_code',
#     recipients_data=recipients,
#     subject_template='$name,您的$product已发货',
#     content_template='尊敬的$name:\n\n您购买的$product已发货,请注意查收。\n\n祝好!'
# )

8. 定时发送

import schedule
import time
from datetime import datetime

def send_daily_report():
    """发送每日报告"""
    print(f"[{datetime.now()}] 发送每日报告...")
    # 这里调用发送邮件的函数
    # send_email_with_attachments(...)
    print("报告发送完成!")

def send_weekly_summary():
    """发送周报"""
    print(f"[{datetime.now()}] 发送周报...")
    # send_email_with_attachments(...)
    print("周报发送完成!")

# 设置定时任务
schedule.every().day.at("09:00").do(send_daily_report)  # 每天9点
schedule.every().monday.at("08:00").do(send_weekly_summary)  # 每周一8点
schedule.every(2).hours.do(send_daily_report)  # 每2小时

# 运行调度器
def run_scheduler():
    """运行定时任务调度器"""
    print("定时任务调度器已启动...")
    while True:
        schedule.run_pending()
        time.sleep(60)  # 每分钟检查一次

# run_scheduler()

# 使用Windows任务计划程序(推荐)
# 1. 创建Python脚本 send_report.py
# 2. 使用任务计划程序设置定时运行
# 命令:python C:\path\to\send_report.py

9. 常见问题与解决

❌ 问题1:连接超时

# 解决:检查网络,尝试不同端口
import socket
socket.setdefaulttimeout(30)  # 设置超时时间

# 或使用TLS而不是SSL
server = smtplib.SMTP(smtp_server, 587)
server.starttls()
server.login(sender_email, sender_password)

❌ 问题2:认证失败

# 解决:
# 1. 确认使用的是授权码而不是登录密码
# 2. 确认已开启SMTP服务
# 3. QQ邮箱:设置 -> 账户 -> POP3/SMTP服务 -> 开启 -> 获取授权码
# 4. 163邮箱:设置 -> POP3/SMTP/IMAP -> 开启 -> 设置授权码

❌ 问题3:中文乱码

# 解决:确保使用UTF-8编码
from email.header import Header

message['Subject'] = Header(subject, 'utf-8')
message['From'] = Header(f"发件人名称 <{sender_email}>", 'utf-8')

❌ 问题4:附件名乱码

# 解决:使用RFC 2231编码
attachment.add_header(
    'Content-Disposition',
    'attachment',
    filename=('utf-8', '', '中文文件名.xlsx')
)

❌ 问题5:被识别为垃圾邮件

# 解决:
# 1. 添加发送间隔
# 2. 使用正规的邮件服务器
# 3. 避免敏感词汇
# 4. 添加退订链接
# 5. 设置正确的发件人信息

10. 实战案例

案例:自动发送日报

"""
实战案例:自动生成并发送日报邮件
"""
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from datetime import datetime, timedelta
from pathlib import Path

class DailyReportSender:
    """日报发送器"""
    
    def __init__(self, smtp_config):
        self.smtp_config = smtp_config
    
    def generate_report_html(self, data):
        """生成报告HTML"""
        html = f"""
        <!DOCTYPE html>
        <html>
        <head>
            <style>
                body {{ font-family: Arial, sans-serif; }}
                .header {{ background: #4472C4; color: white; padding: 15px; }}
                .content {{ padding: 20px; }}
                table {{ border-collapse: collapse; width: 100%; margin: 15px 0; }}
                th, td {{ border: 1px solid #ddd; padding: 10px; text-align: left; }}
                th {{ background: #4472C4; color: white; }}
                .highlight {{ color: #e74c3c; font-weight: bold; }}
                .success {{ color: #27ae60; }}
            </style>
        </head>
        <body>
            <div class="header">
                <h2>📊 {data['title']}</h2>
                <p>{data['date']}</p>
            </div>
            <div class="content">
                <h3>一、今日概况</h3>
                <ul>
                    <li>完成任务:<span class="success">{data['completed_tasks']}</span> 项</li>
                    <li>进行中:{data['in_progress']} 项</li>
                    <li>待处理:{data['pending']} 项</li>
                </ul>
                
                <h3>二、详细数据</h3>
                <table>
                    <tr>
                        <th>指标</th>
                        <th>今日</th>
                        <th>昨日</th>
                        <th>变化</th>
                    </tr>
        """
        
        for metric in data['metrics']:
            change_class = 'success' if metric['change'] >= 0 else 'highlight'
            change_symbol = '+' if metric['change'] >= 0 else ''
            html += f"""
                    <tr>
                        <td>{metric['name']}</td>
                        <td>{metric['today']}</td>
                        <td>{metric['yesterday']}</td>
                        <td class="{change_class}">{change_symbol}{metric['change']}%</td>
                    </tr>
            """
        
        html += f"""
                </table>
                
                <h3>三、备注</h3>
                <p>{data.get('notes', '无')}</p>
                
                <p style="color: #666; font-size: 12px; margin-top: 30px;">
                    此邮件由系统自动发送 | 发送时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
                </p>
            </div>
        </body>
        </html>
        """
        return html
    
    def send_report(self, recipients, data, attachments=None):
        """发送报告"""
        # 生成HTML
        html_content = self.generate_report_html(data)
        
        # 创建邮件
        message = MIMEMultipart()
        message['From'] = self.smtp_config['sender']
        message['To'] = ', '.join(recipients)
        message['Subject'] = f"{data['title']} - {data['date']}"
        
        # 添加HTML正文
        message.attach(MIMEText(html_content, 'html', 'utf-8'))
        
        # 添加附件
        if attachments:
            for file_path in attachments:
                path = Path(file_path)
                if path.exists():
                    with open(path, 'rb') as f:
                        attachment = MIMEApplication(f.read())
                        attachment.add_header(
                            'Content-Disposition',
                            'attachment',
                            filename=('utf-8', '', path.name)
                        )
                        message.attach(attachment)
        
        # 发送
        try:
            server = smtplib.SMTP_SSL(
                self.smtp_config['server'],
                self.smtp_config['port']
            )
            server.login(
                self.smtp_config['sender'],
                self.smtp_config['password']
            )
            server.sendmail(
                self.smtp_config['sender'],
                recipients,
                message.as_string()
            )
            server.quit()
            print(f"日报发送成功!收件人:{recipients}")
            return True
        except Exception as e:
            print(f"发送失败:{e}")
            return False

# 使用示例
if __name__ == '__main__':
    # SMTP配置
    smtp_config = {
        'server': 'smtp.qq.com',
        'port': 465,
        'sender': '[email protected]',
        'password': 'your_auth_code'
    }
    
    # 报告数据
    report_data = {
        'title': '每日工作报告',
        'date': datetime.now().strftime('%Y年%m月%d日'),
        'completed_tasks': 5,
        'in_progress': 3,
        'pending': 2,
        'metrics': [
            {'name': '处理工单', 'today': 25, 'yesterday': 20, 'change': 25},
            {'name': '响应时间(分)', 'today': 15, 'yesterday': 18, 'change': -17},
            {'name': '客户满意度', 'today': 95, 'yesterday': 92, 'change': 3},
        ],
        'notes': '今日系统运行正常,无重大问题。'
    }
    
    # 发送
    sender = DailyReportSender(smtp_config)
    # sender.send_report(
    #     recipients=['[email protected]'],
    #     data=report_data,
    #     attachments=['daily_data.xlsx']
    # )

11. 总结

🔑 核心要点

知识点要点
SMTP使用smtplib发送邮件
邮件格式MIMEText(纯文本/HTML), MIMEMultipart(附件)
附件MIMEApplication添加附件
Outlookwin32com.client调用Outlook
模板string.Template或Jinja2
批量发送添加延迟,避免被识别为垃圾邮件
定时发送schedule库或系统任务计划

✅ 学习检查清单

  • 能发送简单文本邮件
  • 能发送HTML格式邮件
  • 能发送带附件的邮件
  • 能批量发送个性化邮件
  • 了解定时发送的实现方式

📖 下一步学习

掌握了邮件自动化后,让我们学习Python Socket通信:


常见问题 FAQ

💬 发邮件总是被当垃圾邮件怎么办?

确保:1)使用正规SMTP服务器(公司邮箱或Gmail);2)设置正确的发件人名称;3)不要频率过高;4)邮件内容避免纯链接或纯图片。企业环境建议用Exchange/Outlook API。

💬 怎么发送带Excel附件的邮件?

email.mime.base.MIMEBase添加附件,设置Content-Disposition为attachment。常见坑:附件路径要用绝对路径,文件名含中文时需要编码。


系列导航

End of file.