3

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 <abcd@example.com>'
msg['To'] = 'PQRS <pqrs@example.org>'

# 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 <long.random.number@example.com>
# 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

zar30
  • 51
  • 7

1 Answers1

3

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 <abcd@example.com>"
msg["To"] = "PQRS <pqrs@example.org>"

# 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.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.

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • I'm not in a place where I can properly test this, so I might have messed something up. In particular the logic to trim off the brokets around each `cid` with `[1:-1]` and then add it back in the MIME part's `cid` field seems a bit tortured and slightly error-prone. – tripleee Jul 02 '22 at 17:32
  • so I created two loops one list comprehension for the image_cid and the other one similar to your code for the loop where you use enumerate. It works now. Thanks! – zar30 Jul 02 '22 at 19:05
  • I tried this but I get an error saying "AttributeError: 'str' object has no attribute 'add_related'" Any suggestions on the fix? – Mikee Jul 15 '22 at 16:18
  • @Mikee Sorry, part of the code was a copy/paste from an earlier answer; try now. I'm afraid I'm not in a place where I can properly debug this at the moment. – tripleee Jul 15 '22 at 17:32
  • It is working now, though I had to put in block brackets for the arguments of enumerate. You can put an answer at my original post at https://stackoverflow.com/questions/72986198/policy-attribute-error-when-sending-email-by-smtp?noredirect=1#comment128925225_72986198 for me to accept. – Mikee Jul 15 '22 at 20:23
  • @Mikee Thanks for the feedback; updated the answer some more. Unfortunately, we are not allowed to post duplicate answers, and removing the PIL call doesn't really explain what was wrong with it. – tripleee Jul 16 '22 at 03:59
  • I posted an answer to the linked question with a slightly different approach with a loop over the images, adding each into the HTML dynamically. I'm guessing many future visitors will prefer that solution. – tripleee Jul 16 '22 at 05:01