Search code examples
pythonemail-attachmentssmtplib

Python - How to send an html email and attach an image with Python?


I am getting an "error: ValueError: Cannot convert mixed to alternative."

I get this error when I insert the open image and msg.add_attachment block (highlighted in btw #### ####). Without it, the code runs fine. I need to send the email as html and with the image attachment.

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")

For the end result, I need to be able to send an email via Python and attach images.


Solution

  • An e-mail can consist of a single part, or it can be a multi-part message. If it is a multi-part message, it will usually be either a multipart/alternative, or a multipart/mixed.

    • multipart/alternative means there are 2 or more versions of the same content (e.g. plain text and html)
    • multipart/mixed is used when multiple different contents need to be packed together (e.g. an email and an attachment)

    What actually happens when multipart is used is that email consists of a "multipart" container which contains additional parts, e.g. for text+html it is something like this:

    • multipart/alternative part
      • text/plain part
      • text/html part

    In case of an email with attachment, you can have something like this:

    • multipart/mixed part
      • text/plain part
      • image/png part

    So, the container is either mixed or alternative, but cannot be both. So, how to have both? You can nest them, e.g.:

    • multipart/mixed part
      • multipart/alternative part
        • text/plain part
        • text/html part
      • image/png part

    So, now you have an email which consists of a message and an attachment, and the message has both plain text and html.


    Now, in code, this is the basic idea:

    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')
    

    BTW, you can then see what is in each part this way:

    >>> 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==--