15

I currently have a program that will randomly select quotes from a list and email them. I'm now trying to embed an image in the email as well. I've come across a problem where I can attach the email but my quotes no longer work. I have researched online and the solutions are not working for me. Note that I am using Python 3.2.2.

Any guidance would be appreciated.

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage

attachment = 'bob.jpg' 

msg = MIMEMultipart()
msg["To"] = to_addr
msg["From"] = from_addr
msg["Subject"] = subject_header

#msgText = MIMEText(<b>%s</b><br><img src="cid:bob.jpg"><br>, 'html') % body

fp = open(attachment, 'rb')
img = MIMEImage(fp.read())
fp.close()

msg.attach(img)

#email_message = '%s\n%s\n%s' % (subject_header, body, img)
email_message = '%s\n%s' % (subject_header, body)

emailRezi = smtplib.SMTP(mail_server, mail_server_port)
emailRezi.set_debuglevel(1)
emailRezi.login(mail_username, mail_password)
emailRezi.sendmail(from_addr, to_addr, email_message)
#emailRezi.sendmail(from_addr, to_addr, msg.as_string())
emailRezi.quit()

As you can tell from the code above I've tried different ways (referencing the #)

Paul
  • 537
  • 2
  • 5
  • 15
  • 1
    since emails are almost always displayed in HTML nowadays including an image is not the problem, but whether the end user will see it or not is another matter because they are almost always blocked unless specified otherwise – Tules Oct 13 '11 at 14:17
  • 1
    Please post the code you are using. – cdeszaq Oct 13 '11 at 14:17
  • 1
    possible duplicate of [Python: Sending Multipart html emails which contain embedded images](http://stackoverflow.com/questions/920910/python-sending-multipart-html-emails-which-contain-embedded-images) – Jeff Bauer Mar 11 '14 at 19:08

2 Answers2

46

You are going through royal pains to construct a valid MIME message in msg, then ditching it and sending a simple string email_message instead.

You should probably begin by understanding what the proper MIME structure looks like. A multipart message by itself has no contents at all, you have to add a text part if you want a text part.

The following is an edit of your script with the missing pieces added. I have not attempted to send the resulting message. However, see below for a modernized version.

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText  # Added
from email.mime.image import MIMEImage

attachment = 'bob.jpg'

msg = MIMEMultipart()
msg["To"] = to_addr
msg["From"] = from_addr
msg["Subject"] = subject

msgText = MIMEText('<b>%s</b><br/><img src="cid:%s"/><br/>' % (body, attachment), 'html')   
msg.attach(msgText)   # Added, and edited the previous line

with open(attachment, 'rb') as fp:
    img = MIMEImage(fp.read())
img.add_header('Content-ID', '<{}>'.format(attachment))
msg.attach(img)

print(msg.as_string()) # or go ahead and send it

(I also cleaned up the HTML slightly.)

Since Python 3.6, Python's email library has been upgraded to be more modular, logical, and orthogonal (technically since 3.3 already really, but in 3.6 the new version became the preferred one). New code should avoid the explicit creation of individual MIME parts like in the above code, and probably look more something like

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

attachment = 'bob.jpg'

msg = EmailMessage()
msg["To"] = to_addr
msg["From"] = from_addr
msg["Subject"] = subject

attachment_cid = make_msgid()

msg.set_content(
    '<b>%s</b><br/><img src="cid:%s"/><br/>' % (
        body, attachment_cid[1:-1]), 'html')

with open(attachment, 'rb') as fp:
    msg.add_related(
        fp.read(), 'image', 'jpeg', cid=attachment_cid)

# print(msg.as_string()), or go ahead and send

You'll notice that this is quite similar to the "asparagus" example from the email examples documentation in the Python standard library.

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • Awesome. Thank you for assisting with this. The email sends with the picture as a red X box in the body of the email. – Paul Oct 13 '11 at 16:46
  • I should add what my fix was. In addition to the adds from tripleee, I put my img on imgur and referenced the url. – Paul Oct 13 '11 at 17:51
  • 4
    The proper fix if you really want to attach an image is to add the correct file name to the attachment's metadata. http://stackoverflow.com/questions/920910/python-sending-multipart-html-emails-which-contain-embedded-images has a more complete example. – tripleee Oct 13 '11 at 18:34
  • Thanks for the link. I think the nice part about referencing the image from a URL is the flexibility of what system we decide to run this program from. Thanks again for your help. – Paul Oct 13 '11 at 19:17
  • 1
    Needs to be: `img.add_header('Content-ID', '<{}>'.format(attachment))` – wieczorek1990 Nov 12 '15 at 12:24
  • @wieczorek1990 Can't remember off-hand, but I took your word for it, and updated the answer. I use old-style `%` string interpolation elsewhere -- the rest of the code should probably use `.format()` as well for consistency and Python 3 compatibility. – tripleee Nov 12 '15 at 12:58
  • @tripleee I had to do this today. First found your answer then this one helped me to get it right: http://stackoverflow.com/questions/25231051/embedding-image-in-email-python – wieczorek1990 Nov 12 '15 at 18:52
  • this works well in web based and outlook emails, but on iOS Mail app it shows as a "tap to download" icon.. any ideas ? – Sonic Soul Apr 03 '19 at 17:33
  • Maybe try adding a "Content-Disposition: inline" header to the image; but I suspect it's entirely a client-side thing, probably depending on image size. – tripleee Apr 03 '19 at 18:04
  • The second answer doesn't work, only adds an image as an attachment . – Zack Plauché Nov 11 '21 at 14:45
  • 1
    @ZackPlauché Thanks for noticing, I had forgotten to trim the brokets around the `cid:` link in the HTML. Fixed now. – tripleee Nov 12 '21 at 11:32
  • FWIW Python actually adds a `Content-Disposition: inline` for the `add_related()` image even when it shouldn't. It seems to be vaguely harmless (a proper HTML renderer would still pull it into the message itself) but I should probably try to prevent that. – tripleee Nov 12 '21 at 11:34
  • I think the explicit MIME sections not only make understanding the SMTP format easier, but make the code more readable – étale-cohomology Mar 03 '22 at 12:24
  • @étale-cohomology Not sure which explicit MIME sections you are alluding to. If you think the old `email` library was somehow more usable, I seriously beg to differ. – tripleee Mar 03 '22 at 12:27
9

I have edited for attaching the image on a message body and HTML template.

import smtplib
from email import encoders
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage

strFrom = 'zzzzzz@gmail.com'
strTo = 'xxxxx@gmail.com'

# Create the root message 

msgRoot = MIMEMultipart('related')
msgRoot['Subject'] = 'test message'
msgRoot['From'] = strFrom
msgRoot['To'] = strTo
msgRoot['Cc'] =cc
msgRoot.preamble = 'Multi-part message in MIME format.'

msgAlternative = MIMEMultipart('alternative')
msgRoot.attach(msgAlternative)

msgText = MIMEText('Alternative plain text message.')
msgAlternative.attach(msgText)

msgText = MIMEText('<b>Some <i>HTML</i> text</b> and an image.<br><img src="cid:image1"><br>KPI-DATA!', 'html')
msgAlternative.attach(msgText)

#Attach Image 
fp = open('test.png', 'rb') #Read image 
msgImage = MIMEImage(fp.read())
fp.close()

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

import smtplib
smtp = smtplib.SMTP()
smtp.connect('smtp.gmail.com') #SMTp Server Details
smtp.login('exampleuser', 'examplepass') #Username and Password of Account
smtp.sendmail(strFrom, strTo, msgRoot.as_string())
smtp.quit()
Sachin
  • 1,460
  • 17
  • 24
  • Hey, this works! However, you're using the "compat32" API. I can't figure out how to use the more "modern" EmailMessage interface to do the same thing, would you know how to do that? – musbur Feb 24 '21 at 17:14
  • @musbur, Till now I haven't explored the same. – Sachin Apr 14 '21 at 10:30
  • My answer now contains a variation for the modern `email` API in Python 3.6+ – tripleee Mar 28 '22 at 05:13