Python中发送带附件邮件需用email模块构造MIME结构:以MIMEMultipart为根容器,MIMEText添加正文,MIMEBase配合base64编码处理二进制附件,务必rb模式读取文件并设置正确Content-Disposition头。

用 smtplib + email 构建带附件的邮件体
直接调用 smtplib.SMTP 发送纯文本邮件很简单,但带附件必须手动构造 MIME 结构——Python 的 email 模块就是干这个的。MIMEMultipart 是容器,MIMEText 放正文,MIMEBase(配合 encoders.encode_base64)处理二进制附件。
常见错误是把附件当普通字符串读取(比如用 open(...).read()),结果乱码或发送失败;正确做法是用 rb 模式读取,再 attach 进 MIME 对象。
- 附件路径必须存在且有读取权限,相对路径建议用
os.path.abspath转成绝对路径避免定时任务里 cwd 不一致 - 中文附件名要加
Header编码,否则 Outlook 会显示问号:header = Header('报表.xlsx', 'utf-8').encode() - 如果附件大于 10MB,SMTP 服务器通常会拒绝,得提前检查
os.path.getsize(file_path)
用 schedule 或 APScheduler 实现定时触发
schedule 轻量、易读,适合每天固定时间发一次;APScheduler 支持持久化、多线程/协程、错失任务补偿,适合生产环境。
容易踩的坑是:定时器启动后主线程退出,任务根本没执行。必须加 while True: time.sleep(3600) 或用 BlockingScheduler。
立即学习“Python免费学习笔记(深入)”;
-
schedule.every().day.at("09:00").do(send_email_with_attachment)—— 注意时间格式是 24 小时制,且本地时区生效 - 用
APScheduler时,别在add_job里传带括号的调用(如send_email_with_attachment()),要传函数对象本身 - 如果定时任务需依赖数据库或网络,加
max_instances=1防止同一时刻多个实例并发执行
处理 SMTP 登录失败和附件发送超时
真实环境中,smtplib.SMTPAuthenticationError 和 smtplib.SMTPServerDisconnected 最常见——前者是密码错或未开 SMTP 权限(比如 QQ 邮箱要单独生成授权码),后者常因连接空闲超时或防火墙拦截。
附件较大时,send_message() 可能卡住,必须设 timeout 参数。
- 初始化 SMTP 客户端时务必指定
timeout=30:smtplib.SMTP(host, port, timeout=30) - Gmail/Outlook 等要求 OAuth2 或应用专用密码,不能直接用邮箱登录密码;国内企业邮箱常需开启 SMTP 服务并配置白名单 IP
- 建议在发送前用
server.noop()检测连接是否还活着,断了就重连
Linux 后台长期运行时的路径与权限问题
用 systemd 或 nohup 启动脚本后,当前工作目录不再是开发时的目录,./data/report.xlsx 很可能打不开;同时,后台进程无权访问用户桌面会话里的密钥代理(比如 GNOME Keyring),导致无法读取凭据文件。
最稳妥的方式是:所有路径写死为绝对路径,密码等敏感信息用环境变量或独立配置文件(chmod 600),不要硬编码。
- 用
__file__定位脚本所在目录:BASE_DIR = os.path.dirname(os.path.abspath(__file__)),再拼接附件路径 - 定时任务日志必须重定向,否则看不到报错:
nohup python mailer.py > /var/log/mailer.log 2>&1 & - 如果用 systemd,注意
User=字段要设成有文件读取权限的用户,且WorkingDirectory=必须显式声明


















