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

Python发送Office365加密邮件(OME)时附件丢失问题求助

问题原因及解决办法:Office365 OME加密邮件附件不可见

从你描述的现象来看,核心问题出在邮件MIME结构的层次不符合Office365邮件加密(OME)的解析要求。让我拆解一下原因和修复方案:

为什么会出现这个问题?

Office365的OME服务对邮件的MIME结构有严格的层级要求:

  • 当你用启用了全量OME的账号发送邮件时,OME会对整个邮件内容进行加密处理,但它只识别特定层级的MIME容器中的附件。
  • 你的代码中把附件直接附加到了multipart/related类型的根容器中——这个容器本来是用来承载HTML内容和内嵌图片这类“关联资源”的,OME不会把这里的内容识别为独立附件。
  • 普通账号发送时没有OME加密,邮件客户端(如Outlook)会宽松解析MIME结构,所以附件能正常显示;手动通过Outlook/web发送时,系统会自动构建符合OME要求的MIME层次,因此附件也没问题。

修复方案:调整MIME结构层级

正确的MIME结构应该是:

  1. 最外层容器:multipart/mixed——用来承载邮件的“主内容块”和所有独立附件
  2. 主内容块:multipart/related——用来装HTML/纯文本内容、内嵌图片这类和正文关联的资源
  3. 附件:直接附加到最外层的multipart/mixed容器中

下面是修改后的完整代码:

import os
import mimetypes
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.application import MIMEApplication

def sendHTMLEmail( emaillist, subject, body, altBody, logger, mailuser, mailpassword, fileAttachments=None, embeddedImages=None ): 
    """ 
    Send an html-enabled email to a number of recipients. 
    This method includes the options of embedding images in the email, attaching files to the email, 
    and providing alternative plain text in case of an error rendering the HTML. 

    # Arguments 
    emaillist (list[string]): List of email addresses to send to 
    subject (string): Subject line of the email 
    body (string): Body of the email. This can be plain text or HTML, depending on the email. 
    altBody (string): Alternate body text of the email. This will be displayed if the HTML cannot be rendered. 
    logger (JvpyLog): optional logger override parameter 
    mailuser (string): SMTP username to send the mail as 
    mailpassword (string): login password of the SMTP user 
    fileAttachments (list[string]): list of fully qualified filenames to attach to the email 
    embeddedImages (list[dict]): list of embeddedImage dicts specifying images to be embedded in the HTML boddy of the email 

    # Special Types 
    ###### embeddedImages [dict]: 
    ```python 
    { 
        filename (string): fully qualified filename of the image 
        imageid (string): unique id for the image. This will be refrernced in the HTML body of the email and repaced by the actual image. 
    } 
    ``` 
    """ 
    logger.info("Sending email to the following recipients: " + str(emaillist)) 
    # Define these once; use them twice! 
    strFrom = mailuser 
    strTo = ", ".join(emaillist) 

    # 最外层用multipart/mixed,承载主内容和附件
    msgRoot = MIMEMultipart('mixed')
    msgRoot['Subject'] = subject 
    msgRoot['From'] = strFrom 
    msgRoot['To'] = strTo 
    msgRoot.preamble = 'This is a multi-part message in MIME format.' 

    # 创建multipart/related容器,专门放正文和内嵌图片
    msgRelated = MIMEMultipart('related')
    msgRoot.attach(msgRelated)

    # 封装纯文本和HTML内容到alternative容器
    msgAlternative = MIMEMultipart('alternative') 
    msgRelated.attach(msgAlternative)

    msgText = MIMEText(altBody) 
    msgAlternative.attach(msgText) 

    # 加入HTML正文
    msgText = MIMEText(body, 'html') 
    msgAlternative.attach(msgText) 

    # 内嵌图片:附加到related容器
    if embeddedImages is not None: 
        for image in embeddedImages: 
            logger.info("Embedding Image: " + str(image['filename']) + " as content id: " + str(image['imageid'])) 
            fp = open(image['filename'], 'rb') 
            msgImage = MIMEImage(fp.read()) 
            fp.close() 
            # 定义图片的Content-ID,对应HTML中的引用
            msgImage.add_header('Content-ID', image['imageid']) 
            msgRelated.attach(msgImage) 

    # 添加附件:直接附加到最外层的mixed容器
    if fileAttachments is not None: 
        for filename in fileAttachments: 
            logger.info("Attaching File: " + str(filename)) 
            part = MIMEApplication( open(filename,"rb").read(), Name=os.path.basename(filename) ) 
            part['Content-Disposition'] = 'attachment; filename="{0}"'.format(os.path.basename(filename)) 
            # 优化:添加默认MIME类型,避免未知扩展名报错
            ext = os.path.basename(filename).lower().split(".")[-1]
            mimetype = mimetypes.types_map.get(f".{ext}", 'application/octet-stream')
            part['Content-Type'] = f'{mimetype}; name="{os.path.basename(filename)}"' 
            msgRoot.attach(part) 

    # 发送邮件
    server = smtplib.SMTP('smtp.office365.com', 587) 
    server.ehlo() 
    server.starttls() 
    server.ehlo() 
    server.login(mailuser, mailpassword) 
    server.sendmail(strFrom, emaillist, msgRoot.as_string()) 
    server.quit() 
    logger.info("email sent.") 

额外优化说明

我在附件的MIME类型获取部分加了默认值application/octet-stream,避免遇到未知扩展名时抛出KeyError,让代码更健壮。

验证逻辑

修改后,启用OME的账号发送邮件时,OME会正确识别multipart/mixed容器中的附件,收件人登录Office365后就能正常看到附件了;内嵌图片的逻辑不受影响,依然能正常展示。

内容的提问来源于stack exchange,提问作者Tucker Yates

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:13:45