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

使用Python通过Gmail发送HTML邮件时嵌入式图片不显示

Python发送Gmail HTML邮件部分图片无法显示排查与修复

问题背景

使用Python通过Gmail发送带HTML模板的邮件时,部分图片无法正常显示,但单独发送图片可正常工作。发送HTML片段时图片显示正常,发送完整HTML模板则出现问题,尝试过Stack Overflow相关方案未解决。

发送邮件代码片段

try:
    with SMTP_SSL('smtp.gmail.com', 465) as smtp:
        username, password = cred[0], cred[1]
        self.message_label.setText('Checking credentials...')
        sleep(1)
        try:
            smtp.login(username, password)
            succeed = 0
            for i in range(1):  # change to len(data_contacts[0])
                test = True
                receiver = data_contacts[0][i]
                sleep(1)
                msg_root = MIMEMultipart('related')

                msg_root['From'] = username
                msg_root['To'] = receiver
                msg_root['Subject'] = data_contacts[1][i]

                msg_alternative = MIMEMultipart('alternative')
                msg_root.attach(msg_alternative)

                try:
                    with open(f'{data_contacts[2][i]}.html', 'r') as file:
                        contents = file.readlines()
                    contents = ''.join(contents)
            
                    pattern = r'src="(.*?)"'
                    match = set(findall(pattern, contents))
                    print(match)
                    print(pattern)
                    ids = {}
                    for index, path in enumerate(match):
                        ids[path] = f"image_{index}"

                    for path in match:
                        contents = sub(rf"{path}", rf"cid:{ids[path]}", contents)
                    with open('test_html.html', 'w') as f:
                        f.write(contents)

                    msg_text = MIMEText(contents, 'html', 'utf-8')
                    msg_alternative.attach(msg_text)

                    self.message_label.setText(f'Sending {i+1}/{len(data_contacts[0])}')
                    sleep(1)
                    try:
                        list_images = listdir(f'{data_contacts[2][i]}')
                        for image in list_images:
                            with open(f'{data_contacts[2][i]}/{image}', 'rb') as file:
                                msgImage = MIMEImage(file.read(), name = basename(image))
                                path = f'{data_contacts[2][i]}/{image}'
                                try:
                                    msgImage.add_header('Content-ID', f'<{ids[path]}>')
                                    msg_root.attach(msgImage)
                                except:
                                    get_exception()
                                    test = False
                        smtp.sendmail(username, receiver, msg_root.as_string())
                        self.message_label.setText('Sent')
                        succeed += 1
                        sleep(1)
                        self.notifyProgress.emit(int((i+1)*100/(len(data_contacts[0]))))
                    except Exception as e:
                        self.message_label.setText(f'{data_contacts[2][i]} folder must exists')
                        sleep(1)
                        self.message_label.setText('')
                        test = False
                except Exception as e:
                    self.message_label.setText(f'{e} {data_contacts[2][i]}.html not found')
                    print((f'{e} {data_contacts[2][i]}.html not found'))
                    sleep(1)
                    self.message_label.setText('')
                    test = False
                if i != len(data_contacts[0])-1 and test:
                    self.message_label.setText(f'Waiting {delay} seconds')
                    sleep(delay)
                else:
                    self.notifyProgress.emit(int((i+1)*100/(len(data_contacts[0]))))
                    sleep(1)
            self.notifyProgress.emit(100)
            sleep(1)
            self.message_label.setText('')
            sleep(1)
            self.notifyProgress.emit(0)
        except:
            self.message_label.setText(f"Can't login in {username}")
            get_exception()
except:
    self.message_label.setText("Can't connect to server")

关键问题排查与修复方案

1. 正则匹配与路径映射错误

  • 问题点:
    • 使用set(findall(pattern, contents))会丢失重复图片的匹配结果,导致重复图片无法绑定Content-ID;
    • 正则src=&quot;(.*?)&quot;仅匹配转义后的双引号,若HTML中存在未转义的双引号或路径格式不一致,会导致部分路径未被捕获;
    • 本地图片路径拼接(f'{data_contacts[2][i]}/{image}')与HTML中提取的路径不匹配,导致ids[path]找不到对应键。
  • 修复:
    • 移除set,保留有序去重的匹配结果,避免遗漏重复图片:
      match = findall(pattern, contents)
      match = list(dict.fromkeys(match))
      
    • 先对HTML内容进行实体转义还原,确保路径提取准确:
      import html
      contents = html.unescape(''.join(contents))
      pattern = r'src="([^"]+)"'  # 匹配正常双引号的src属性
      
    • 确保HTML中的路径与本地图片的相对路径一致,比如HTML中是logo.png,则用文件名作为ids的键:
      ids = {}
      for index, filename in enumerate(match):
          ids[filename] = f"image_{index}"
      # 遍历图片时直接用文件名匹配
      for image in list_images:
          # ... 后续绑定Content-ID
          msgImage.add_header('Content-ID', f'<{ids[image]}>')
      

2. Content-ID绑定与MIME结构优化

  • 问题点:部分邮箱客户端对图片的MIME头要求严格,仅设置Content-ID可能不足以正常显示。
  • 修复:添加Content-Disposition头指定图片为内联显示:
    msgImage.add_header('Content-Disposition', 'inline', filename=basename(image))
    msgImage.add_header('Content-ID', f'<{ids[path]}>')
    

3. HTML模板兼容性检查

  • 问题点:完整HTML模板可能存在标签格式错误、图片路径无效、或使用了不被邮箱支持的CSS/JS导致渲染异常。
  • 修复:
    • 检查生成的test_html.html文件,确认所有cid替换正确;
    • 移除HTML中不必要的JS、复杂CSS,改用邮箱客户端兼容的内联样式;
    • 测试单张异常图片的格式(如是否为WebP等小众格式),转换为JPG/PNG后重试。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 09:10:34