Home > Net >  How to add multiple embedded images to an email in Python?
How to add multiple embedded images to an email in Python?

Time:07-03

This question is really a continuation of this answer https://stackoverflow.com/a/49098251/19308674. I'm trying to add multiple embedded images (not just one) to the email content.

I want to do it in a way that I loop through a list of images, in addition, there will be different text next to each image. Something like this for example as you can see in Weather Next 10 days I want to loop through images from a folder and next to each image there will be some different text as in the example.

from email.message import EmailMessage
from email.utils import make_msgid
import mimetypes

msg = EmailMessage()

# generic email headers
msg['Subject'] = 'Hello there'
msg['From'] = 'ABCD <[email protected]>'
msg['To'] = 'PQRS <[email protected]>'

# set the plain text body
msg.set_content('This is a plain text body.')

# now create a Content-ID for the image
image_cid = make_msgid(domain='example.com')
# if `domain` argument isn't provided, it will 
# use your computer's name

# set an alternative html body
msg.add_alternative("""\
<html>
    <body>
        <p>This is an HTML body.<br>
           It also has an image.
        </p>
        <img src="cid:{image_cid}">
    </body>
</html>
""".format(image_cid=image_cid[1:-1]), subtype='html')
# image_cid looks like <[email protected]>
# to use it as the img src, we don't need `<` or `>`
# so we use [1:-1] to strip them off


# now open the image and attach it to the email
with open('path/to/image.jpg', 'rb') as img:

    # know the Content-Type of the image
    maintype, subtype = mimetypes.guess_type(img.name)[0].split('/')

    # attach it
    msg.get_payload()[1].add_related(img.read(), 
                                         maintype=maintype, 
                                         subtype=subtype, 
                                         cid=image_cid)


# the message is ready now
# you can write it to a file
# or send it using smtplib

CodePudding user response:

If I'm able to guess what you are trying to ask, the solution is simply to generate a unique cid for each image.

from email.message import EmailMessage
from email.utils import make_msgid
# import mimetypes

msg = EmailMessage()

msg["Subject"] = "Hello there"
msg["From"] = "ABCD <[email protected]>"
msg["To"] = "PQRS <[email protected]>"

# create a Content-ID for each image
image_cid = [make_msgid(domain="example.com")[1:-1],
    make_msgid(domain="example.com")[1:-1],
    make_msgid(domain="example.com")[1:-1]]

msg.set_content("""\
<html>
    <body>
        <p>This is an HTML body.<br>
           It also has three images.
        </p>
        <img src="cid:{image_cid[0]}"><br/>
        <img src="cid:{image_cid[1]}"><br/>
        <img src="cid:{image_cid[2]}">
    </body>
</html>
""".format(image_cid=image_cid), subtype='html')

for idx, imgtup in enumerate(
        ("path/to/first.jpg", "jpeg"),
        ("file/name/of/second.png", "png"),
        ("path/to/third.gif", "gif")):
    imgfile, imgtype = imgtup
    with open(imgfile, "rb") as img:
        msg.get_payload()[0].add_related(
            img.read(), 
            maintype="image", 
            subtype=imgtype, 
            cid=f"<{image_cid[idx]}>")

# The message is ready now.
# You can write it to a file
# or send it using smtplib

Kudos for using the modern EmailMessage API; we still see way too many questions which blindly copy/paste the old API from Python <= 3.5 with MIMEMultipart etc etc.

I took out the mimetypes image format quessing logic in favor of spelling out the type of each image in the code. If you need Python to guess, you know how to do that, but for a small static list of images, it seems to make more sense to just specify each, and avoid the overhead as well as the unlikely but still not impossible problem of having the heuristics guess wrong.

I'm guessing your images will all use the same format, and so you could actually simply hardcode subtype="png" or whatever.

It should hopefully be obvious how to add more per-image information into the loop over image tuples, though if your needs go beyond the trivial, you'll probably want to encapsulate the image and its various attributes into a simple class.

Your message apparently makes no sense for a recipient who cannot access the HTML part, so I took out the bogus text/plain part you had. You were effectively sending a different message entirely to recipients whose preference is to view plain text over HTML; if that was genuinely your intent, please stop it. If you are unable to provide the same information in the plain text version as in the HTML version, at least don't make it look to those recipients like you had nothing of importance to say in the first place.

Tangentially, please don't fake email addresses of domains you don't own. You will end up tipping off the spammers and have them trying to send unsolicited messages to an innocent third party. Always use IANA-reserved domains like example.com, example.org etc which are guaranteed to never exist in reality. I edited your question to fix this.

  • Related