使用smtplib发送带附件Outlook邮件触发SSL Unknown Protocol错误
故障根因
- 报错的核心原因是加密连接模式和端口规则完全错配。Office365 SMTP服务的端口对应加密规则有明确要求:
- 587端口为标准STARTTLS端口:TCP连接建立后先走明文通信,收到客户端发送的
STARTTLS指令后才升级为加密通道 - 465端口为隐式SSL端口:TCP连接建立的第一时间就要求完成SSL加密握手,全程不传输明文内容
- 587端口为标准STARTTLS端口:TCP连接建立后先走明文通信,收到客户端发送的
- 你当前代码的错误操作是:给等待明文指令的587端口使用了
smtplib.SMTP_SSL(隐式SSL连接),连接建立瞬间就向服务端发送SSL握手报文,服务端无法识别这类非明文协议报文,直接抛出[SSL: UNKNOWN_PROTOCOL]错误。 - 之前适配Google SMTP时能正常运行,本质是当时用的是465隐式SSL端口,匹配了SMTP_SSL的连接逻辑,切到Office365的时候没有对应调整端口加密规则,才触发的异常。
修正实现方案
方案1:沿用587端口的STARTTLS模式(官方推荐兼容方案)
将原来的SMTP_SSL连接替换为普通明文SMTP连接,保留TLS升级流程即可:
def send_email(self, html_message): formatted_date = datetime.datetime.now().strftime('%Y%m%d') msg_root = MIMEMultipart('related') msg_root['Subject'] = self.client.client_name + 'Test Forecast' msg_root['From'] = self.config.get_email_from recipients = self.client.recipient_email recipients = recipients.split(', ') body = MIMEText(html_message, 'html') msg_root.attach(body) filename = self.client.city + ', ' + self.client.state # PDF附件逻辑 if self.client.use_pdf: filepath = self.config.get_email_pdf_file_path fp = open(filepath, 'rb') att = email.mime.application.MIMEApplication(fp.read(), _subtype="pdf") fp.close() att.add_header('Content-Disposition', 'attachment', filename=filename + '.pdf') msg_root.attach(att) # 587端口匹配STARTTLS规则 port = 587 context = ssl.create_default_context() # 用普通SMTP类建立连接,替换原有的SMTP_SSL with smtplib.SMTP("smtp.office365.com", port) as server: server.ehlo() # 触发TLS加密升级 server.starttls(context=context) # 加密升级完成后需要重新执行EHLO,否则会触发鉴权失败 server.ehlo() server.login(self.config.get_email_from, self.config.get_email_password) print("Sending email report...") server.sendmail(self.config.get_email_from, recipients, msg_root.as_string()) print("Your email was sent!")
方案2:保留隐式SSL连接逻辑
如果不想改动原来的SMTP_SSL连接代码,直接将端口从587替换为465即可,这个端口下隐式SSL的连接模式和服务端规则完全匹配:
# 端口替换为465后,原有SMTP_SSL连接逻辑可正常运行 port = 465 context = ssl.create_default_context() with smtplib.SMTP_SSL("smtp.office365.com", port, context=context) as server: server.login(self.config.get_email_from, self.config.get_email_password) server.sendmail(self.config.get_email_from, recipients, msg_root.as_string())
常见踩坑提醒
- 不要在
SMTP_SSL的隐式SSL连接里重复调用starttls:此时连接已经全程加密,重复发送STARTTLS指令会被服务端直接拒绝 - 企业域账号如果开启了多因素身份验证(MFA),需要为SMTP服务生成独立的应用密码,不能直接使用账号的日常登录密码鉴权
- 如果修正加密模式后依然出现同类协议报错,记得排查本地到
smtp.office365.com的链路是否有流量拦截、代理篡改SSL握手报文的情况,这类链路问题也会触发UNKNOWN_PROTOCOL异常
内容的提问来源于stack exchange,提问作者Zach Rieck
相关产品推荐
相关产品推荐

