Python - 如何使用Python发送包含图片的HTML电子邮件?

4

我遇到了"错误: ValueError: Cannot convert mixed to alternative."错误。

当我插入打开图片和msg.add_attachment块(在#### ####中突出显示)时,我会遇到这个错误。如果不插入它,代码就可以正常运行。我需要以html格式发送电子邮件并附带图像附件。

import os
import imghdr
from email.message import EmailMessage
import smtplib


EMAIL_ADDRESS = os.environ.get('EMAIL-USER')
EMAIL_PASSWORD = os.environ.get('EMAIL-PASS')

Message0 = "HelloWorld1"
Message1 = "HelloWorld2"

msg = EmailMessage()
msg['Subject'] = 'Hello WORLD'
msg['From'] = EMAIL_ADDRESS
msg['To'] = EMAIL_ADDRESS

msg.set_content('This is a plain text email, see HTML format')

########################################################################
with open('screenshot.png', 'rb') as f:
    file_data = f.read()
    file_type = imghdr.what(f.name)
    file_name = f.name

msg.add_attachment(file_data, maintype='image', subtype=file_type, filename=file_name)
#########################################################################

msg.add_alternative("""\
    <!DOCTYPE html>
    <html>
        <body>
            <h1 style="color:Blue;">Hello World</h1>
                {Message0}
                {Message1}
        </body>
    </html>
    """.format(**locals()), subtype='html')

with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
    smtp.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
    smtp.send_message(msg)
    print("email sent")

我的最终目标是能够通过Python发送电子邮件并附加图片。

2个回答

3
电子邮件可以由单一部分组成,也可以是多部分消息。如果是多部分消息,则通常为 multipart/alternativemultipart/mixed
  • multipart/alternative 表示同一内容有两个或更多版本(例如纯文本和HTML)。
  • multipart/mixed 用于将多个不同的内容打包在一起(例如电子邮件和附件)。
实际上,在使用 multipart 时,电子邮件由一个包含其他部分的“多部分”容器组成。例如对于文本+HTML,它看起来像这样:
  • multipart/alternative 部分
    • text/plain 部分
    • text/html 部分
在带有附件的电子邮件中,您可以像这样拥有以下内容:
  • multipart/mixed 部分
    • text/plain 部分
    • image/png 部分
因此,容器要么是 mixed,要么是 alternative,但不能同时存在。那么如何同时拥有两者呢?您可以嵌套它们,例如:
  • multipart/mixed 部分
    • multipart/alternative 部分
      • text/plain 部分
      • text/html 部分
    • image/png 部分
因此,现在您拥有一个由消息和附件组成的电子邮件,并且消息具有纯文本和html。
现在,在代码中,这是基本的想法:
msg = EmailMessage()
msg['Subject'] = 'Subject'
msg['From'] = 'from@email'
msg['To'] = 'to@email'

msg.set_content('This is a plain text')
msg.add_attachment(b'xxxxxx', maintype='image', subtype='png', filename='image.png')

# Now there are plain text and attachment.
# HTML should be added as alternative to the plain text part:

text_part, attachment_part = msg.iter_parts()
text_part.add_alternative("<p>html contents</p>", subtype='html')

顺便提一下,你可以通过以下方式查看每个部分中的内容:
>>> plain_text_part, html_text_part = text_part.iter_parts()
>>> print(plain_text_part)
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit

This is a plain text

>>> print(html_text_part)
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0

<p>html contents</p>

>>> print(attachment_part)
Content-Type: image/png
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="image.png"
MIME-Version: 1.0

eHh4eHh4

>>> print(msg)
Subject: Subject
From: from@email
To: to@email
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="===============2219697219721248811=="

--===============2219697219721248811==
Content-Type: multipart/alternative;
 boundary="===============5680305804901241482=="

--===============5680305804901241482==
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit

This is a plain text

--===============5680305804901241482==
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0

<p>html contents</p>

--===============5680305804901241482==--

--===============2219697219721248811==
Content-Type: image/png
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="image.png"
MIME-Version: 1.0

eHh4eHh4

--===============2219697219721248811==--

有关multipart的更多详细信息,请参阅RFC 2046 - zvone
@ zvone 感谢您提供的代码和解释。我已经成功添加了一个附件。但是,当我尝试添加多个附件(例如几张图片)时,使用 msg.iter_parts() 函数会出现以下错误:ValueError: too many values to unpack (expected 2)。 - HelloWorld123

0

这是对@zvone提供的示例的补充。这里的区别在于,我想要一封包含两个版本的邮件。一个是带有图像附件的纯文本版本。另一个部分是嵌入图像的HTML版本。

因此,邮件的布局如下:

  • multipart/mixed 部分
    • multipart/alternative 部分
      • multipart/related 部分 (带有图像的HTML)
        • text/html 部分
        • image/png 部分
      • text/plain 部分 (纯文本)
    • image/png 部分 (附件)

纯文本版本:textfile.txt

Hello World

HTML版本:mail_content.html

<html>
  <head></head>
  <body>
    <h1>Hello World</h1>
    <img src="cid:{graph_example_img_cid}" />
  </body>
</html>

当然还有 Python 代码(已测试过 Python 3.10 版本)。图像的嵌入遵循最新的 Python 标准库中的 email 示例。

Python 代码:main.py

# var
mail_server: str = "****smtp****"
user: str = "****@****.com"
me = user
you = user

# imports
import smtplib # mail actual sending function
import imghdr # And imghdr to find the types of our images
from email.message import EmailMessage
from email.utils import make_msgid
import datetime

# Open a plain text file for reading.  For this example, assume that
# the text file contains only ASCII characters.
textfile_filepath: str = "textfile.txt"
with open(textfile_filepath) as fp:
    # Create a text/plain message
    msg = EmailMessage()
    msg.set_content(fp.read())

# me == the sender's email address
# you == the recipient's email address
msg['Subject'] = 'Test Email with 2 versions'
msg['From'] = me
msg['To'] = you

# attachements
# Open the files in binary mode.  Use imghdr to figure out the
# MIME subtype for each specific image.
image_filepath: str = "test_image.png"
with open(image_filepath, 'rb') as fp:
    img_data = fp.read()
    msg.add_attachment(img_data, maintype='image', subtype=imghdr.what(None, img_data))

text_part, attachment_part = msg.iter_parts()

## HTML alternative using Content ID
html_content_filepath: str = "mail_content.html"
graph_cid = make_msgid()
with open(html_content_filepath) as fp:
    # Create a text/plain message
    #msg = MIMEText(fp.read())
    raw_html: str = fp.read()
    # note that we needed to peel the <> off the msgid for use in the html.
    html_content: str = raw_html.format(graph_example_img_cid=graph_cid[1:-1])

    # add to email message 
    text_part.add_alternative(html_content,subtype='html')

# Now add the related image to the html part.
with open(image_filepath, 'rb') as img:
    text_part, html_part = text_part.iter_parts()
    html_part.add_related(img.read(), 'image', 'jpeg', cid=graph_cid)

# Send the message via our own SMTP server, but don't include the envelope header.
s = smtplib.SMTP(mail_server)
s.sendmail(me, [you], msg.as_string())
s.quit()

# log
current_time = datetime.datetime.now()
print("{} >>> Email sent (From: {}, To: {})".format(
    current_time.isoformat(), me, you
))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接