35👍
Update
Many thanks to Saqib Ali for resurrecting this old question nearly 5 years after my reply.
The instructions I gave at the time no longer work. I suspect there have been some improvements to Django in the intervening years which mean that send_mail()
enforces plain text. No matter what you put in the content, it will always be delivered as plain text.
The most recent Django documentation explains that send_mail()
is really just a convenience for creating an instance of the django.core.mail.EmailMessage
class, and then calling send()
on that instance. EmailMessage
has this note for the body parameter, which explains the results we’re seeing now in 2014:
body: The body text. This should be a plain text message.
… somewhat later in the docs …
By default, the MIME type of the body parameter in an EmailMessage is "text/plain". It is good practice to leave this alone.
Fair enough (I confess I haven’t taken the time to investigate why the 2009 instructions worked – I did test them back in 2009 – or when it changed). Django does provide, and document, a django.core.mail.EmailMultiAlternatives
class to make it easier for sending a plain text and HTML representation of the same message.
The case in this question is slightly different. We’re not seeking to append an alternative per se, but to append related parts to one of the alternatives. Within the HTML version (and it doesn’t matter if you have or omit the plain text version), we want to embed an image data part. Not an alternative view of the content, but related content that is referenced in the HTML body.
Sending an embedded image is still possible, but I don’t see a straightforward way to do it using send_mail
. It’s time to dispense with the convenience function and to instantiate an EmailMessage
directly.
Here’s an update to the previous example:
from django.core.mail import EmailMessage
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# Load the image you want to send as bytes
img_data = open('logo.jpg', 'rb').read()
# Create a "related" message container that will hold the HTML
# message and the image. These are "related" (not "alternative")
# because they are different, unique parts of the HTML message,
# not alternative (html vs. plain text) views of the same content.
html_part = MIMEMultipart(_subtype='related')
# Create the body with HTML. Note that the image, since it is inline, is
# referenced with the URL cid:myimage... you should take care to make
# "myimage" unique
body = MIMEText('<p>Hello <img src="cid:myimage" /></p>', _subtype='html')
html_part.attach(body)
# Now create the MIME container for the image
img = MIMEImage(img_data, 'jpeg')
img.add_header('Content-Id', '<myimage>') # angle brackets are important
img.add_header("Content-Disposition", "inline", filename="myimage") # David Hess recommended this edit
html_part.attach(img)
# Configure and send an EmailMessage
# Note we are passing None for the body (the 2nd parameter). You could pass plain text
# to create an alternative part for this message
msg = EmailMessage('Subject Line', None, 'foo@bar.com', ['bar@foo.com'])
msg.attach(html_part) # Attach the raw MIMEBase descendant. This is a public method on EmailMessage
msg.send()
Original reply from 2009:
To send an e-mail with embedded images, use python’s built-in email module to build up the MIME parts.
The following should do it:
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# Load the image you want to send at bytes
img_data = open('logo.jpg', 'rb').read()
# Create a "related" message container that will hold the HTML
# message and the image
msg = MIMEMultipart(_subtype='related')
# Create the body with HTML. Note that the image, since it is inline, is
# referenced with the URL cid:myimage... you should take care to make
# "myimage" unique
body = MIMEText('<p>Hello <img src="cid:myimage" /></p>', _subtype='html')
msg.attach(body)
# Now create the MIME container for the image
img = MIMEImage(img_data, 'jpeg')
img.add_header('Content-Id', '<myimage>') # angle brackets are important
msg.attach(img)
send_mail(subject, msg.as_string(), from, [to], priority="high")
In reality, you’ll probably want to send the HTML along with a plain-text alternative. In that case, use MIMEMultipart to create the "related" mimetype container as the root. Then create another MIMEMultipart with the subtype "alternative", and attach both a MIMEText (subtype html) and a MIMEText (subtype plain) to the alternative part. Then attach the image to the related root.
2👍
I was having trouble with Jarret’s recipe on Django 1.10 – was getting MIME and encoding errors for the various ways you can attach MIME data.
Here’s a simple multipart transactional template for an email with an embedded coupon_image
file object that works on django 1.10:
from django.core.mail import EmailMultiAlternatives
from email.mime.image import MIMEImage
def send_mail(coupon_image):
params = {'foo':'bar'} # create a template context
text_body = render_to_string('coupon_email.txt', params)
html_body = render_to_string('coupon_email.html', params)
img_data = coupon_image.read() #should be a file object, or ImageField
img = MIMEImage(img_data)
img.add_header('Content-ID', '<coupon_image>')
img.add_header('Content-Disposition', 'inline', filename="coupon_image")
email = EmailMultiAlternatives(
subject="Here's your coupon!",
body=text_body,
from_email='noreply@example.com',
to=['someone@example.com',]
)
email.attach_alternative(html_body, "text/html")
email.mixed_subtype = 'related'
email.attach(img)
email.send(fail_silently=False)
0👍
Version using a text alternative
Jarret Hardie’s answer says "You could pass plain text to create an alternative part for this message". However, I have found that doing so would lead gmail (though not outlook) to display both parts of the email, one after the other, as described here
As recommended there and elsewhere, I simply used EmailMultiAlternatives
. The resulting code looks somewhat like this:
from django.core.mail import EmailMultiAlternatives
from email.mime.image import MIMEImage
# Load the image you want to send as bytes
img_data = open('logo.jpg', 'rb').read()
# Create the body with HTML. Note that the image, since it is inline, is
# referenced with the URL cid:myimage... you should take care to make
# "myimage" unique
html_content = '<p>Hello <img src="cid:myimage" /></p>'
text_content = 'Hello'
# Configure an EmailMultiAlternatives
msg = EmailMultiAlternatives('Subject Line', text_content, 'foo@bar.com', ['bar@foo.com'])
msg.attach_alternative(html_content)
# Now create the MIME container for the image
img = MIMEImage(img_data, 'jpeg')
img.add_header('Content-Id', '<myimage>') # angle brackets are important
img.add_header("Content-Disposition", "inline", filename="myimage") # David Hess recommended this edit
msg.attach(img)
# Finally, send the whole thing.
msg.send()
- Django application 504 error after saving model
- How can I insert parameters in raw SQL in Django Python
- How to profile django channels?