101

I've been playing around with the email module in python but I want to be able to know how to embed images which are included in the html.

So for example if the body is something like

<img src="../path/image.png"></img>

I would like to embed image.png into the email, and the src attribute should be replaced with content-id. Does anybody know how to do this?

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
John Jiang
  • 11,069
  • 12
  • 51
  • 60

4 Answers4

187

Here is an example I found.

Recipe 473810: Send an HTML email with embedded image and plain text alternate:

HTML is the method of choice for those wishing to send emails with rich text, layout and graphics. Often it is desirable to embed the graphics within the message so recipients can display the message directly, without further downloads.

Some mail agents don't support HTML or their users prefer to receive plain text messages. Senders of HTML messages should include a plain text message as an alternate for these users.

This recipe sends a short HTML message with a single embedded image and an alternate plain text message.

# Send an HTML email with an embedded image and a plain text message for
# email clients that don't want to display the HTML.

from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.MIMEImage import MIMEImage

# Define these once; use them twice!
strFrom = 'from@example.com'
strTo = 'to@example.com'

# Create the root message and fill in the from, to, and subject headers
msgRoot = MIMEMultipart('related')
msgRoot['Subject'] = 'test message'
msgRoot['From'] = strFrom
msgRoot['To'] = strTo
msgRoot.preamble = 'This is a multi-part message in MIME format.'

# Encapsulate the plain and HTML versions of the message body in an
# 'alternative' part, so message agents can decide which they want to display.
msgAlternative = MIMEMultipart('alternative')
msgRoot.attach(msgAlternative)

msgText = MIMEText('This is the alternative plain text message.')
msgAlternative.attach(msgText)

# We reference the image in the IMG SRC attribute by the ID we give it below
msgText = MIMEText('<b>Some <i>HTML</i> text</b> and an image.<br><img src="cid:image1"><br>Nifty!', 'html')
msgAlternative.attach(msgText)

# This example assumes the image is in the current directory
fp = open('test.jpg', 'rb')
msgImage = MIMEImage(fp.read())
fp.close()

# Define the image's ID as referenced above
msgImage.add_header('Content-ID', '<image1>')
msgRoot.attach(msgImage)

# Send the email (this example assumes SMTP authentication is required)
import smtplib
smtp = smtplib.SMTP()
smtp.connect('smtp.example.com')
smtp.login('exampleuser', 'examplepass')
smtp.sendmail(strFrom, strTo, msgRoot.as_string())
smtp.quit()
Community
  • 1
  • 1
Andrew Hare
  • 344,730
  • 71
  • 640
  • 635
  • Much thanks, I've tried many solution, This is the one that works perfect! – wukong Aug 23 '13 at 08:49
  • @Andrew Hare: There are two multipart views (the two msgText instances). I don't see the content-type specified for each. How would the receiving system know which to render for the Html one? – Old Geezer Nov 16 '14 at 14:58
  • I added msgText.replace_header('Content-Type','text/html') for the second msgText instance. – Old Geezer Nov 17 '14 at 01:14
  • 2
    Ftr: the second argument to the [`MIMEText` constructor](https://docs.python.org/2/library/email.mime.html#email.mime.text.MIMEText) is the subtype (defaults to `plain`, is `'html'` for the second instance). – dtk Feb 11 '16 at 23:17
  • I tired the same code for sending multiple embedded images in the content, but the first image gets embedded and the rest doesn't gets embedded, how can we embed multiple images? – Shoban Sundar Feb 02 '18 at 06:17
  • You should typically **not** mess with the MIME `preamble`. The one scenario where overriding the default preamble minght make sense is if you expect your recipients to not understand the English default message (but even then you only have access to the basic ASCII character repertoire). It is only likely to be seen by people who are stuck on email software from the previous millennium anyway. – tripleee Nov 18 '18 at 08:02
  • 15
    It worked for me in python 3.7.2, but I had to write imports differently: `from email.mime.text import MIMEText` `from email.mime.image import MIMEImage` `from email.mime.multipart import MIMEMultipart` – senya Mar 19 '19 at 09:09
  • The recommended answer works perfectly but just make sure that you **don't have spaces in cid:image1 ** while making your HTML template. it should be exactly this :- `` not `` or `` otherwise it won't work. Atleast it didn't work in my case when I was using it with jinja template. @AndrewHare if this is correct please update your answer. – yawningphantom Apr 02 '19 at 07:45
  • Special characters finally worked on python 3.7 adding encode('utf-8') to msgRoot at sendmail: smtp.sendmail(strFrom, strTo, msgRoot.as_string().encode('utf-8')) – Al Martins Nov 02 '20 at 12:22
  • I keep getting 2 images, the actual image (automatically added to the bottom of the email... for some reason) and a little missing image icon above it (that I can only assume is the string with the ``. How are you ONLY rendering the `` that's in the given html string and what is 'image1' in this context? A file name like `my_image.jpg` or something else? – Zack Plauché Nov 08 '21 at 18:06
  • I tried running this example, but I am getting a ton of import errors. – Evan Gertis Mar 23 '23 at 20:27
91

For Python versions 3.4 and above.

The accepted answer is excellent, but only suitable for older Python versions (2.x and 3.3). I think it needs an update.

Here's how you can do it in newer Python versions (3.4 and above):

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

# 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='xyz.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@xyz.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
xyres
  • 20,487
  • 3
  • 56
  • 85
9

I realized how painful some of the things are with SMTP and email libraries and I thought I have to do something with it. I made a library that makes embedding images to HTML way easier:

from redmail import EmailSender
email = EmailSender(host="<SMTP HOST>", port=0)

email.send(
    sender="me@example.com",
    receivers=["you@example.com"]
    subject="An email with image",
    html="""
        <h1>Look at this:</h1>
        {{ my_image }}
    """, 
    body_images={
        "my_image": "path/to/image.png"
    }
)

Sorry for promotion but I think it's pretty awesome. You can supply the image as Matplotlib Figure, Pillow Image or just as bytes if your image is in those formats. It uses Jinja for templating.

If you need to control the size of the image, you can also do this:

email.send(
    sender="me@example.com",
    receivers=["you@example.com"]
    subject="An email with image",
    html="""
        <h1>Look at this:</h1>
        <img src="{{ my_image.src }}" width=200 height=300>
    """, 
    body_images={
        "my_image": "path/to/image.png"
    }
)

You can just pip install it:

pip install redmail

It's (hopefully) all you need for email sending (has a lot more) and it is well tested. I also wrote extensive documentation: https://red-mail.readthedocs.io/en/latest/ and source code is found here.

miksus
  • 2,426
  • 1
  • 18
  • 34
-4

Code working

    att = MIMEImage(imgData)
    att.add_header('Content-ID', f'<image{i}.{imgType}>')
    att.add_header('X-Attachment-Id', f'image{i}.{imgType}')
    att['Content-Disposition'] = f'inline; filename=image{i}.{imgType}'
    msg.attach(att)
  • 2
    Hi! Thanks for sharing answer. It might be useful if you also will add some explanations about the code above. Aslo in OP's code I do not see `imgType` variable definition so your code will raise an exception. – Charnel Feb 03 '20 at 19:48