You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用smtplib发送带附件Outlook邮件触发SSL Unknown Protocol错误

故障根因
  • 报错的核心原因是加密连接模式和端口规则完全错配。Office365 SMTP服务的端口对应加密规则有明确要求:
    • 587端口为标准STARTTLS端口:TCP连接建立后先走明文通信,收到客户端发送的STARTTLS指令后才升级为加密通道
    • 465端口为隐式SSL端口:TCP连接建立的第一时间就要求完成SSL加密握手,全程不传输明文内容
  • 你当前代码的错误操作是:给等待明文指令的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.24 15:15:04