日常办公中最枯燥的重复劳动之一就是发邮件——给几十个人发通知、发周报、发附件。用 Python 自动化发邮件,一个是快,另一个是不会漏发或发错。
一、准备工作——开启邮箱的 SMTP 服务
发邮件需要开启邮箱的 SMTP 服务,获得一个授权码。不是在代码里写你的登录密码。
常见邮箱的设置
| 邮箱 | SMTP 服务器 | 端口(SSL) | 开启方式 |
|---|---|---|---|
| QQ邮箱 | smtp.qq.com | 465 | 设置→账号→开启SMTP→生成授权码 |
| 163邮箱 | smtp.163.com | 465 | 设置→POP3/SMTP→开启→设置授权码 |
| Gmail | smtp.gmail.com | 465 | 需要开启两步验证+应用专用密码 |
| 企业微信 | smtp.exmail.qq.com | 465 | 管理员后台开启 |
授权码就是你的"邮箱密码",但只在代码里用,不要外传。
二、用 smtplib 发邮件(标准库)
smtplib是 Python 内置库,不用安装,但代码写起来稍复杂。
1. 发送纯文本邮件
importsmtplibfromemail.mime.textimportMIMETextfromemail.headerimportHeader# 发件人配置smtp_server="smtp.qq.com"smtp_port=465sender="your_name@qq.com"password="你的授权码"# 不是QQ密码!# 收件人receiver="someone@example.com"# 构造邮件subject="本周工作汇报"content=""" 张老师,你好: 以下是本周工作总结: 1. 完成了秒杀系统的 Redis 集成 2. 发布了 5 篇 CSDN 技术文章 3. 正在准备考研数学复习 祝好! """# 创建邮件对象msg=MIMEText(content,"plain","utf-8")msg["From"]=Header(f"张政 <{sender}>")msg["To"]=Header(f"{receiver}")msg["Subject"]=Header(subject,"utf-8")# 发送try:withsmtplib.SMTP_SSL(smtp_server,smtp_port)asserver:server.login(sender,password)server.sendmail(sender,[receiver],msg.as_string())print("邮件发送成功!")exceptExceptionase:print(f"发送失败:{e}")2. 发送 HTML 邮件
html_content=""" <h2>本周工作汇报</h2> <table border="1" cellpadding="5"> <tr> <th>项目</th> <th>进度</th> </tr> <tr> <td>秒杀系统</td> <td style="color:green;">已完成</td> </tr> <tr> <td>CSDN 文章</td> <td>5 篇/周</td> </tr> <tr> <td>考研复习</td> <td>进行中</td> </tr> </table> <p>祝好!</p> """msg=MIMEText(html_content,"html","utf-8")3. 发送带附件的邮件
importsmtplibfromemail.mime.multipartimportMIMEMultipartfromemail.mime.textimportMIMETextfromemail.mime.baseimportMIMEBasefromemailimportencodersimportosdefsend_email_with_attachment(sender,password,receiver,subject,body,file_path):"""发送带附件的邮件"""# 创建 multipart 邮件对象msg=MIMEMultipart()msg["From"]=sender msg["To"]=receiver msg["Subject"]=subject# 邮件正文msg.attach(MIMEText(body,"plain","utf-8"))# 附件withopen(file_path,"rb")asf:attachment=MIMEBase("application","octet-stream")attachment.set_payload(f.read())encoders.encode_base64(attachment)# 设置附件头(文件名)filename=os.path.basename(file_path)attachment.add_header("Content-Disposition",f"attachment; filename={filename}")msg.attach(attachment)# 发送withsmtplib.SMTP_SSL("smtp.qq.com",465)asserver:server.login(sender,password)server.sendmail(sender,[receiver],msg.as_string())print(f"邮件已发送(含附件:{filename})")# 使用send_email_with_attachment(sender="your_name@qq.com",password="授权码",receiver="boss@company.com",subject="季度报告",body="请查收季度报告附件。",file_path="季度报告.xlsx")三、用 yagmail(推荐,代码精简很多)
pipinstallyagmailimportyagmail# 连接邮箱(只需配置一次)yag=yagmail.SMTP(user="your_name@qq.com",password="授权码",host="smtp.qq.com",port=465,)# 发送邮件(一行搞定)yag.send(to="someone@example.com",subject="本周工作汇报",contents=""" 张老师,你好: 以下是本周工作总结: 1. 完成秒杀系统 2. 发了5篇CSDN """,)# 发送带附件yag.send(to=["boss@company.com","cc@company.com"],# 支持多个收件人和抄送subject="季度报告",contents="请查收附件。",attachments=["季度报告.xlsx","图表.png"],# 附件列表)print("发送成功!")对比 smtplib:yagmail 代码量减少 60% 以上,不用手动构造 MIME 对象,附件自动处理。
四、群发邮件——逐人发送 vs 群发单显
1. 逐人发送(收件人互不可见)
importyagmailimportpandasaspd yag=yagmail.SMTP("your_name@qq.com","授权码",host="smtp.qq.com")# 读取收件人列表(姓名+邮箱)df=pd.read_excel("收件人名单.xlsx")for_,rowindf.iterrows():content=f"""{row['name']}老师,你好: 请查收本周的教学计划安排。 教学办 """yag.send(to=row["email"],subject="本周教学计划通知",contents=content,)print(f"已发送给{row['name']}({row['email']})")2. 带模板的群发
# 模板字符串template=""" {name}老师,你好: 教务处通知: {content} 请于 {deadline} 前提交。 教学办 """# 数据data=[{"name":"张三","content":"请提交期末试卷","deadline":"6月30日"},{"name":"李四","content":"请提交教学总结","deadline":"7月5日"},{"name":"王五","content":"请确认下学期的课程安排","deadline":"7月10日"},]yag=yagmail.SMTP("your_name@qq.com","授权码")foritemindata:content=template.format(**item)# 这里需要每个人的邮箱,实际中可以从 Excel 或数据库获取# yag.send(to=item['email'], subject='教务处通知', contents=content)# print(f"已发送给{item['name']}")五、定时发送
结合系统定时任务,可以实现每天 9 点自动发邮件:
Windows 定时任务
# send_daily_report.pyimportyagmailimportdatetimedefsend_report():today=datetime.date.today().strftime("%Y-%m-%d")yag=yagmail.SMTP("your_name@qq.com","授权码")content=f"""{today}系统运行报告: 1. 服务器状态:正常 2. 数据库连接:正常 3. 日志数量:1256 条 """yag.send(to="admin@company.com",subject=f"系统日报 -{today}",contents=content,)print(f"{today}日报已发送")if__name__=="__main__":send_report()然后在 Windows 的任务计划程序中设置每天 9:00 执行此脚本。
六、常见问题
1. 发送失败:535 Error:登录失败
# 原因:不是用 QQ 密码,而是用授权码# 解决:去 QQ 邮箱设置 → 账号 → 生成授权码# 注意:授权码中间没有空格2. 发送失败:554 被判定为垃圾邮件
# 原因:内容太短、太多链接、发送频率太高# 解决:# - 每封邮件的正文不要太雷同# - 加收件人姓名个性化# - 控制发送频率(每分钟不超过 10 封)importtimefor_,rowindf.iterrows():yag.send(...)time.sleep(3)# 每封邮件间隔 3 秒3. QQ邮箱发送上限
# QQ邮箱免费版:每天 500 封# 企业邮箱:每天 2000 封# 超过会被封号 24 小时,建议分多个邮箱发送七、完整案例:自动发送周报
importyagmailimportpandasaspdfromdatetimeimportdatetimeclassWeeklyReporter:"""自动周报发送器"""def__init__(self,sender_email,auth_code):self.yag=yagmail.SMTP(sender_email,auth_code,host="smtp.qq.com")defgenerate_report(self,name,tasks):"""生成个性化周报"""task_list="\n".join([f"{i+1}.{t}"fori,tinenumerate(tasks)])returnf"""{name}老师,你好: 以下是本周工作汇总: 本周完成任务:{task_list}祝工作顺利!{datetime.now().strftime('%Y-%m-%d')}"""defsend_batch(self,recipients):"""批量发送周报"""forrinrecipients:content=self.generate_report(r["name"],r["tasks"])self.yag.send(to=r["email"],subject=f"【周报】{r['name']}- 本周工作总结",contents=content,)print(f"✅ 已发送:{r['name']}")time.sleep(2)print(f"\n共发送{len(recipients)}封周报")# 使用reporter=WeeklyReporter("your_name@qq.com","授权码")# 收件人列表recipients=[{"name":"张三","email":"zhangsan@company.com","tasks":["完成了秒杀系统的Redis集成","发布了3篇CSDN文章"],},{"name":"李四","email":"lisi@company.com","tasks":["完成了数据库设计文档","修复了3个线上Bug"],},]reporter.send_batch(recipients)总结
简单发送 → yagmail(推荐,3行代码搞定) 带附件 → yagmail 自动处理 群发 → 循环发送 + 模板替换 定时 → 脚本 + 系统定时任务工作中 90% 的邮件发送需求,用 yagmail 就够了。关键是把收件人和模板数据准备好,发送本身几行代码的事。
💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。