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.com | 465/587 |
| 163邮箱 | smtp.163.com | 465/994 |
| Gmail | smtp.gmail.com | 587 |
| Outlook | smtp.office365.com | 587 |
| 企业邮箱 | 根据企业配置 | - |
⚠️ 注意:大多数邮箱需要开启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添加附件 |
| Outlook | win32com.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。常见坑:附件路径要用绝对路径,文件名含中文时需要编码。
� 系列导航
- 上一篇:18 - Python操作PPT
- 当前:19 - Python邮件自动化
- 下一篇:20 - Python Socket通信