← 返回列表

自动定时发送邮件

通过 SMTP 协议配置邮件服务器,实现定时向指定收件人列表发送带格式的通知邮件。常用于系统报警或每日工作汇报的自动化流程。

快速预览:

import smtplib
from email.mime.text import MIMEText
msg = MIMEText("这是自动发送的测试邮件内容")
msg['Subject'] = "每日系统报告"
with smtplib.SMTP('smtp.server.com', 587) as server:
    server.login('user@example.com', 'password')
    server.send_message(msg)

应用场景与价值

自动发送邮件是办公自动化的重要组成部分,在多种场景下极具价值:

  • 系统监控报警: 服务器异常、磁盘空间不足等问题的自动通知
  • 定期报表: 每日销售数据、网站流量统计的自动汇总发送
  • 任务提醒: 项目截止日期、会议通知的自动提醒
  • 数据备份通知: 备份任务完成状态的自动报告
  • 批量通知: 向客户群发促销信息、系统维护通知
  • 自动回复: 订单确认、注册验证等自动邮件

通过 Python 实现邮件自动化,可以节省大量重复性工作时间,确保重要信息及时传达,提升工作效率和响应速度。

基础知识

SMTP 协议

SMTP(Simple Mail Transfer Protocol)是发送邮件的标准协议。主要概念:

  • SMTP 服务器: 负责发送邮件的服务器(如 Gmail、QQ 邮箱的 SMTP 服务器)
  • 端口号:
    • 25: 传统 SMTP 端口(多数被封禁)
    • 587: TLS 加密端口(推荐)
    • 465: SSL 加密端口

常见邮箱 SMTP 配置

邮箱服务 SMTP 服务器 端口 说明
Gmail smtp.gmail.com 587 需开启"不够安全的应用访问权限"
QQ 邮箱 smtp.qq.com 587 需开启 SMTP 服务并使用授权码
163 邮箱 smtp.163.com 465 需开启 SMTP 服务并使用授权码
Outlook smtp.office365.com 587 需使用应用密码

email 模块

Python 的 email 模块用于构建邮件内容:

from email.mime.text import MIMEText      # 纯文本邮件
from email.mime.multipart import MIMEMultipart  # 复杂邮件(带附件)
from email.mime.image import MIMEImage    # 图片附件
from email.mime.base import MIMEBase      # 通用附件

代码详解

让我们深入理解邮件发送的每一步:

import smtplib
from email.mime.text import MIMEText

导入必需的模块。smtplib 处理 SMTP 连接,MIMEText 用于创建邮件内容。

msg = MIMEText("这是自动发送的测试邮件内容")

创建邮件对象,参数是邮件正文内容。默认为纯文本格式。

msg['Subject'] = "每日系统报告"

设置邮件主题。这是收件人在收件箱中看到的标题。

with smtplib.SMTP('smtp.server.com', 587) as server:

创建 SMTP 连接。使用 with 语句确保连接自动关闭。587 是 TLS 加密端口。

    server.login('user@example.com', 'password')

使用邮箱账号和密码(或授权码)登录 SMTP 服务器进行身份验证。

    server.send_message(msg)

发送邮件。send_message() 方法会自动从 msg 对象中提取发件人、收件人等信息。

完整代码

基础版本:发送简单文本邮件

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

def send_email(subject, body, to_email, from_email, password, smtp_server, smtp_port):
    """
    发送简单文本邮件

    参数:
        subject: 邮件主题
        body: 邮件正文
        to_email: 收件人邮箱
        from_email: 发件人邮箱
        password: 发件人邮箱密码或授权码
        smtp_server: SMTP 服务器地址
        smtp_port: SMTP 端口
    """
    try:
        # 创建邮件对象
        msg = MIMEMultipart()
        msg['From'] = from_email
        msg['To'] = to_email
        msg['Subject'] = subject

        # 添加邮件正文
        msg.attach(MIMEText(body, 'plain', 'utf-8'))

        # 连接 SMTP 服务器
        with smtplib.SMTP(smtp_server, smtp_port) as server:
            server.starttls()  # 启用 TLS 加密
            server.login(from_email, password)
            server.send_message(msg)

        print(f"邮件发送成功! 收件人: {to_email}")
        return True

    except Exception as e:
        print(f"邮件发送失败: {e}")
        return False


# 使用示例
if __name__ == "__main__":
    # 配置信息
    SMTP_SERVER = 'smtp.qq.com'
    SMTP_PORT = 587
    FROM_EMAIL = 'your_email@qq.com'
    PASSWORD = 'your_authorization_code'  # QQ 邮箱授权码

    # 发送邮件
    send_email(
        subject="每日系统报告",
        body="系统运行正常,今日无异常。",
        to_email="recipient@example.com",
        from_email=FROM_EMAIL,
        password=PASSWORD,
        smtp_server=SMTP_SERVER,
        smtp_port=SMTP_PORT
    )

进阶版本:发送 HTML 格式邮件

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

def send_html_email(subject, html_content, to_emails, from_email, password, smtp_server, smtp_port):
    """
    发送 HTML 格式邮件,支持多个收件人

    参数:
        to_emails: 收件人列表,如 ['user1@example.com', 'user2@example.com']
    """
    try:
        msg = MIMEMultipart('alternative')
        msg['From'] = from_email
        msg['To'] = ', '.join(to_emails)
        msg['Subject'] = subject

        # 添加纯文本版本(备用)
        text_part = MIMEText("此邮件需要 HTML 支持查看", 'plain', 'utf-8')
        msg.attach(text_part)

        # 添加 HTML 版本
        html_part = MIMEText(html_content, 'html', 'utf-8')
        msg.attach(html_part)

        # 发送邮件
        with smtplib.SMTP(smtp_server, smtp_port) as server:
            server.starttls()
            server.login(from_email, password)
            server.send_message(msg)

        print(f"HTML 邮件发送成功! 收件人: {', '.join(to_emails)}")
        return True

    except Exception as e:
        print(f"发送失败: {e}")
        return False


# HTML 邮件模板
html_template = """
<!DOCTYPE html>
<html>
<head>
    <style>
        body {{ font-family: Arial, sans-serif; }}
        .header {{ background-color: #4CAF50; color: white; padding: 20px; }}
        .content {{ padding: 20px; }}
        .footer {{ background-color: #f1f1f1; padding: 10px; text-align: center; }}
        table {{ border-collapse: collapse; width: 100%; }}
        th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
        th {{ background-color: #4CAF50; color: white; }}
    </style>
</head>
<body>
    <div class="header">
        <h1>{title}</h1>
    </div>
    <div class="content">
        <p>报告日期: {date}</p>
        <h2>数据汇总</h2>
        <table>
            <tr>
                <th>指标</th>
                <th>数值</th>
            </tr>
            <tr>
                <td>总销售额</td>
                <td>${total_sales}</td>
            </tr>
            <tr>
                <td>订单数量</td>
                <td>{order_count}</td>
            </tr>
        </table>
    </div>
    <div class="footer">
        <p>此邮件由系统自动生成</p>
    </div>
</body>
</html>
"""

# 使用示例
html_content = html_template.format(
    title="每日销售报告",
    date=datetime.now().strftime('%Y-%m-%d'),
    total_sales="25,340.00",
    order_count=156
)

send_html_email(
    subject="每日销售报告 - 2026-01-09",
    html_content=html_content,
    to_emails=['boss@company.com', 'manager@company.com'],
    from_email='system@company.com',
    password='your_password',
    smtp_server='smtp.company.com',
    smtp_port=587
)

高级版本:发送带附件的邮件

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
import os

def send_email_with_attachment(subject, body, to_email, from_email, password,
                                smtp_server, smtp_port, attachment_path):
    """
    发送带附件的邮件

    参数:
        attachment_path: 附件文件路径
    """
    try:
        msg = MIMEMultipart()
        msg['From'] = from_email
        msg['To'] = to_email
        msg['Subject'] = subject

        # 添加邮件正文
        msg.attach(MIMEText(body, 'plain', 'utf-8'))

        # 添加附件
        if os.path.exists(attachment_path):
            with open(attachment_path, 'rb') as f:
                part = MIMEBase('application', 'octet-stream')
                part.set_payload(f.read())

            encoders.encode_base64(part)
            filename = os.path.basename(attachment_path)
            part.add_header('Content-Disposition', f'attachment; filename= {filename}')
            msg.attach(part)
        else:
            print(f"警告: 附件 {attachment_path} 不存在")

        # 发送邮件
        with smtplib.SMTP(smtp_server, smtp_port) as server:
            server.starttls()
            server.login(from_email, password)
            server.send_message(msg)

        print(f"带附件的邮件发送成功!")
        return True

    except Exception as e:
        print(f"发送失败: {e}")
        return False


# 使用示例
send_email_with_attachment(
    subject="销售报表",
    body="请查收附件中的本月销售数据报表。",
    to_email="manager@company.com",
    from_email="system@company.com",
    password="your_password",
    smtp_server="smtp.company.com",
    smtp_port=587,
    attachment_path="./sales_report.xlsx"
)

定时发送版本

import schedule
import time
from datetime import datetime

def send_daily_report():
    """每日报告任务"""
    print(f"[{datetime.now()}] 开始发送每日报告...")

    # 这里调用前面定义的发送邮件函数
    send_email(
        subject=f"每日报告 - {datetime.now().strftime('%Y-%m-%d')}",
        body="今日系统运行正常。",
        to_email="admin@company.com",
        from_email="system@company.com",
        password="your_password",
        smtp_server="smtp.company.com",
        smtp_port=587
    )


# 配置定时任务
schedule.every().day.at("09:00").do(send_daily_report)  # 每天 9:00 发送
schedule.every().monday.at("10:00").do(send_daily_report)  # 每周一 10:00 发送
schedule.every().hour.do(send_daily_report)  # 每小时发送

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

运行示例

安装依赖

uv add schedule

获取 QQ 邮箱授权码

  1. 登录 QQ 邮箱网页版
  2. 设置 -> 账户 -> POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务
  3. 开启 SMTP 服务
  4. 生成授权码(这个授权码用于代替邮箱密码)

执行发送

uv run main.py

预期输出

邮件发送成功! 收件人: recipient@example.com

扩展思路

1. 批量发送邮件

def send_bulk_emails(recipients, subject, body, from_email, password, smtp_server, smtp_port):
    """批量发送邮件,为每个收件人单独发送"""
    success_count = 0

    for recipient in recipients:
        if send_email(subject, body, recipient, from_email, password, smtp_server, smtp_port):
            success_count += 1
        time.sleep(1)  # 延迟防止被识别为垃圾邮件

    print(f"批量发送完成: {success_count}/{len(recipients)} 成功")

2. 邮件模板系统

from string import Template

# 定义模板
template = Template("""
尊敬的 $name,

您好! 这是一封自动发送的邮件。

订单号: $order_id
总金额: $amount

感谢您的支持!
""")

# 使用模板
for user in users:
    body = template.substitute(
        name=user['name'],
        order_id=user['order_id'],
        amount=user['amount']
    )
    send_email(subject="订单确认", body=body, to_email=user['email'], ...)

3. 错误重试机制

def send_email_with_retry(max_retries=3, **kwargs):
    """带重试的邮件发送"""
    for attempt in range(max_retries):
        try:
            if send_email(**kwargs):
                return True
        except Exception as e:
            print(f"第 {attempt + 1} 次尝试失败: {e}")
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)  # 指数退避
    return False

4. 发送日志记录

import logging

logging.basicConfig(
    filename='email_log.txt',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

def send_email_with_log(**kwargs):
    """带日志的邮件发送"""
    try:
        result = send_email(**kwargs)
        logging.info(f"邮件发送成功: {kwargs['to_email']}")
        return result
    except Exception as e:
        logging.error(f"邮件发送失败: {kwargs['to_email']} - {e}")
        return False

5. 使用配置文件

import json

# config.json
{
    "smtp_server": "smtp.qq.com",
    "smtp_port": 587,
    "from_email": "sender@qq.com",
    "password": "authorization_code"
}

# 读取配置
with open('config.json', 'r') as f:
    config = json.load(f)

send_email(**config, subject="Test", body="Test", to_email="user@example.com")

6. 邮件队列系统

from queue import Queue
from threading import Thread

email_queue = Queue()

def email_worker():
    """邮件发送工作线程"""
    while True:
        email_data = email_queue.get()
        if email_data is None:
            break
        send_email(**email_data)
        email_queue.task_done()

# 启动工作线程
worker = Thread(target=email_worker)
worker.start()

# 添加邮件到队列
email_queue.put({
    'subject': 'Test',
    'body': 'Test',
    'to_email': 'user@example.com',
    # ... 其他参数
})

7. 集成数据分析结果

import pandas as pd

# 从 CSV 读取数据并生成报告
df = pd.read_csv('sales_data.csv')
summary = f"""
每日销售报告

总销售额: ${df['amount'].sum():,.2f}
订单数量: {len(df)}
平均订单额: ${df['amount'].mean():,.2f}
"""

send_email(subject="每日销售报告", body=summary, ...)

注意事项

安全性

  1. 不要硬编码密码: 使用环境变量或配置文件存储敏感信息
  2. 使用授权码: 许多邮箱服务要求使用专门的授权码而非账户密码
  3. 加密连接: 始终使用 TLS/SSL 加密(starttls()

反垃圾邮件

  1. 控制发送频率: 避免短时间内大量发送
  2. 验证收件人: 确保邮箱地址有效
  3. 提供退订选项: 批量邮件应提供退订链接
  4. 遵守法律法规: 遵守 CAN-SPAM 法案和 GDPR 等规定

最佳实践

  1. 异常处理: 妥善处理网络错误、认证失败等异常
  2. 日志记录: 记录发送状态,便于排查问题
  3. 测试环境: 先在测试环境验证,再部署到生产环境
  4. 邮件内容: 确保内容专业、格式正确、无拼写错误

通过掌握自动发送邮件技术,你可以构建强大的通知系统、报表系统和自动化工作流,显著提升工作效率和信息传递的及时性。