Python通过SMTP发送内嵌图片邮件时出现AttributeError: policy错误
错误原因
- 直接将PIL库的
Image对象作为邮件部件附加到MIMEMultipart对象中:email库要求attach()方法传入的必须是符合MIME规范的部件对象(即email.mime下的各类MIME实例),PIL Image不属于合法MIME部件,因此邮件序列化时尝试访问MIME对象专属的policy属性就会抛出你看到的AttributeError: policy报错。 - 未做内嵌图片与HTML正文的关联绑定:即使将图片转为合法MIME部件,如果没有给图片设置唯一Content-ID,也没有在HTML正文中通过cid协议引用对应ID的图片,图片要么不会显示,要么会被识别为普通附件,无法实现在正文内嵌展示的效果。
修正方案
- 将所有待内嵌的图片转为
MIMEImage类型的合法MIME部件,不要直接传入PIL Image对象。 - 为每个内嵌图片设置全局唯一的
Content-ID头,同时在HTML正文的<img>标签中用cid:对应ID作为src属性值,建立正文和图片的关联。 - 给图片部件添加
Content-Disposition: inline头,明确标记为内嵌资源,避免邮件客户端将其识别为可下载附件。 - 保持邮件根容器为
multipart/related类型,该类型是专门为携带正文关联资源(如内嵌图片、样式)设计的,不要换成multipart/mixed。
修正后可直接运行的代码
from email.mime.image import MIMEImage from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart import smtplib from io import BytesIO from PIL import Image # 初始化邮件容器 msg = MIMEMultipart("related") msg['Subject'] = subject msg['From'] = from_addr msg['To'] = to_addr # 第一步:生成带图片引用的HTML正文 html_body = """ <html> <body> <p>邮件正文内容:</p> """ cid_mapping = {} for img_idx, image_name in enumerate(plots.keys()): current_cid = f"embed_img_{img_idx}" cid_mapping[image_name] = current_cid # 在HTML中插入图片标签,通过cid引用对应资源 html_body += f'<img src="cid:{current_cid}" alt="{image_name}" style="max-width: 100%;"><br>' html_body += """ </body> </html> """ # 挂载HTML正文部件 msg.attach(MIMEText(html_body, "html", "utf-8")) # 第二步:挂载所有内嵌图片部件 for image_name, image_binary in plots.items(): # 如果不需要对图片做压缩、裁剪、格式转换,直接用原始二进制流即可,无需PIL处理 # 若需要用PIL处理图片,解开下方注释代码 # pil_img = Image.open(BytesIO(image_binary)) # img_buffer = BytesIO() # pil_img.save(img_buffer, format="PNG") # 根据实际图片格式调整 # img_buffer.seek(0) # mime_img = MIMEImage(img_buffer.read()) # 直接使用原始图片二进制的写法 mime_img = MIMEImage(image_binary) # 绑定Content-ID,注意值必须用尖括号包裹 mime_img.add_header('Content-ID', f'<{cid_mapping[image_name]}>') # 标记为内嵌资源 mime_img.add_header('Content-Disposition', 'inline', filename=image_name) msg.attach(mime_img) # 发送邮件 with smtplib.SMTP(host) as smtp_conn: # 若SMTP服务需要TLS加密、账号鉴权,解开下方对应代码 # smtp_conn.starttls() # smtp_conn.login(from_addr, email_password) smtp_conn.sendmail(from_addr, to_addr, msg.as_string())
注意事项
- 若无需对图片做二次处理,不要额外用PIL打开再保存,直接传入原始图片二进制可以减少不必要的性能损耗。
- Content-ID头的值必须用尖括号包裹,HTML中引用cid时不需要加尖括号,直接写
cid:id值即可。 - 如果发送后图片还是被识别为附件,优先检查邮件容器类型是否为
multipart/related,以及Content-Disposition是否设置为inline。
内容的提问来源于stack exchange,提问作者Mikee
相关产品推荐
相关产品推荐

