Gmail SMTP连接失败时,应先检查端口(587或465)和应用专用密码;必须启用两步验证并生成16位应用密码,禁用默认25端口,587端口需调用starttls(),465端口使用SMTP_SSL(),且SMTPAuthenticationError通常源于密码错误或第三方访问被限制。

SMTP连接失败时先检查smtp.gmail.com端口和应用密码
直接用smtplib.SMTP()连不上 Gmail,大概率不是代码问题,而是端口或认证方式不对。Gmail 已停用“用户名+密码”登录,必须用应用专用密码(App Password),且端口必须是 587(STARTTLS)或 465(SSL),不能用默认的 25。
- 开启两步验证后,在 Google 账户设置里生成「应用专用密码」,16位,只用一次
- 连接时显式指定端口:
smtplib.SMTP("smtp.gmail.com", 587),然后立刻调用.starttls() - 若用
SMTP_SSL("smtp.gmail.com", 465),则跳过starttls(),但必须确保服务器支持 SSL - 错误信息
smtplib.SMTPAuthenticationError通常意味着密码错、应用密码未启用,或账户限制了第三方访问
send_message()比sendmail()更安全地处理抄送(CC)字段
用 sendmail() 手动拼接邮件头容易漏掉 CC 字段,导致收件人看不到抄送列表,甚至被某些邮箱拒收。Python 3.6+ 推荐用 email.message.EmailMessage + smtplib.SMTP.send_message(),它自动处理 MIME 头、编码和地址格式。
-
msg["To"]和msg["CC"]都必须是字符串(逗号分隔的邮箱),不能是列表;但send_message()会解析它们,而sendmail()不会 - 抄送部门邮箱建议统一存为变量,例如:
cc_list = ["hr@company.com", "finance@company.com"],再用", ".join(cc_list)赋给msg["CC"] - 别忘了把 CC 地址也加进
send_message()的to_addrs参数里,否则 SMTP 服务器不会实际投递到抄送方——这是最常踩的坑
附件路径含中文或空格时必须用os.path.abspath()和email.utils.make_msgid()
发带附件的邮件时,如果路径是相对路径或含中文,open() 可能报 FileNotFoundError;附件名乱码则是因为没设 Content-Disposition 的 filename*= 参数。
- 始终用
os.path.abspath("report.xlsx")获取绝对路径,避免脚本在不同目录下执行失败 - 附件名需用
email.utils.formataddr()或手动构造Content-Disposition: attachment; filename*=utf-8''%E6%8A%A5%E8%A1%A8.xlsx,否则 Outlook 会显示乱码 - 二进制附件要用
MIMEApplication(非文本)或MIMEBase,并调用.set_payload()+encoders.encode_base64()
定时发送前务必测试msg.as_string()输出是否含完整CC头
很多自动化脚本跑通了却没抄送到部门,根本原因是生成的原始邮件字符串里压根没有 CC: 行——可能因为赋值顺序错(比如先调 as_string() 再设 msg["CC"]),或者用了不兼容的旧版 MIMEMultipart 构造方式。
立即学习“Python免费学习笔记(深入)”;
- 调试时加一句
print(msg.as_string()[:500]),确认输出里有类似CC: hr@company.com, finance@company.com的行 - 避免混用
MIMEMultipart("alternative")(用于 HTML+纯文本)和MIMEMultipart("mixed")(用于带附件),后者才支持嵌套CC头 - 生产环境别依赖本地时区,用
datetime.now(timezone.utc)生成时间戳,防止定时任务因系统时区变更发错时间
msg.as_string() 输出保存成 .eml 文件,用 Outlook 或 Thunderbird 直接打开验证结构。


















