[Django]-Django: How to send HTML emails with embedded images

10👍

http://djangosnippets.org/snippets/285/

You have to use MultiPart and cid:. It is almost always a bad idea to send html mails with images. It gives spam points to your mail and smtp server …

Here is better example: https://djangosnippets.org/snippets/3001/

15👍

I achieved what op is asking for using django’s mailing system. Upsides it that it’ll use django settings for mailing (including a different subsystem for testing, etc. I also use mailhogs during development). It’s also quite a bit higher level:

from django.conf import settings
from django.core.mail import EmailMultiAlternatives


message = EmailMultiAlternatives(
    subject=subject,
    body=body_text,
    from_email=settings.DEFAULT_FROM_EMAIL,
    to=recipients,
    **kwargs
)
message.mixed_subtype = 'related'
message.attach_alternative(body_html, "text/html")
message.attach(logo_data())

message.send(fail_silently=False)

logo_data is a helper function that attaches the logo (the image I wanted to attach in this case):

from email.mime.image import MIMEImage

from django.contrib.staticfiles import finders
from functools import lru_cache


@lru_cache()
def logo_data():
    with open(finders.find('emails/logo.png'), 'rb') as f:
        logo_data = f.read()
    logo = MIMEImage(logo_data)
    logo.add_header('Content-ID', '<logo>')
    return logo

6👍

Remember that django only offer wrappers for standard smtplib – I don’t know if it will help, but try to look at this example: http://code.activestate.com/recipes/473810-send-an-html-email-with-embedded-image-and-plain-t/

So I guess you could use EmailMessage‘s header values to define this ‘image1’ – message header is a dict of values, so just add something like {'Content-ID': '<image1>'} to it.

Then attach the file to your email using attach(). After that you could use the code to generate the html message like this:

html_content = '<b>Some HTML text</b> and an image: <img src="cid:image1">'
👤bx2

0👍

If you want to send email with image as attachment (in my situation it was image that has been caught directly from form, after its saving) you can use the following code as example:

#forms.py

from django import forms
from django.core.mail import EmailMessage
from email.mime.image import MIMEImage


class MyForm(forms.Form):
    #...
    def save(self, *args, **kwargs):
        # In next line we save all data from form as usual.
        super(MyForm, self).save(*args, **kwargs)
        #...
        # Your additional post_save login can be here.
        #...
        # In my case name of field was an "image".
        image = self.cleaned_data.get('image', None)
        # Then we create an "EmailMessage" object as usual.
        msg = EmailMessage(
            'Hello',
            'Body goes here',
            'from@example.com',
            ['to1@example.com', 'to2@example.com'],
            ['bcc@example.com'],
            reply_to=['another@example.com'],
            headers={'Message-ID': 'foo'},
        )
        # Then set "html" as default content subtype.
        msg.content_subtype = "html"
        # If there is an image, let's attach it to message.
        if image:
            mime_image = MIMEImage(image.read())
            mime_image.add_header('Content-ID', '<image>')
            msg.attach(mime_image)
        # Then we send message.
        msg.send()

0👍

I have tried the below code and it worked.

Code:

    msg = EmailMessage()
    
    # generic email headers
    msg['Subject'] = 'Welcome'
    msg['From'] = 'abc@gmail.com'
    recipients = ['abc@gmail.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='')
    # if `domain` argument isn't provided, it will
    # use your computer's name
    
    # set an alternative html body
    msg.add_alternative("""\
        <html>
       <body>
          <table border='0' cellpadding='1' cellspacing='0' width='800'>
             <tbody>
                <tr>
                   <td height='506'>
                      <table border='0' cellpadding='0' cellspacing='0' width='600'>
                         <tbody>
                            <tr>
                               <td valign='top'>
                                  <img height='190' src="cid:{image_cid}" width='800'  tabindex='0'>
                               </td>
                            </tr>
                            <tr>
                               <td height='306' valign='top'>
                                  <table cellpadding='0' cellspacing='20' width='800'>
                                     <tbody>
                                        <tr>
                                           <td align='left' height='804' style='font-family:arial,helvetica,sans-serif;font-size:13px' valign='top'>
                                              Hi {name},<br><br>
                                              Welcome!
                                           </td>
                                        </tr>
                                     </tbody>
                                  </table>
                               </td>
                            </tr>
                         </tbody>
                      </table>
                   </td>
                </tr>
             </tbody>
          </table>
       </body>
    </html>
    """.format(image_cid=image_cid[1:-1],name='ABC'), 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/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)
    server = smtplib.SMTP(host=<hostname>, port=25)
    
    server.starttls()
    # send the message via the server.
    server.sendmail(msg['From'], recipients, msg.as_string())
    
    server.quit()

0👍

My experience according to official documentation on Django 3.1 and storage of files AWS S3. AWS S3 i set as "Public" (Publicly accessible).

Sending only to one user recipient:

I used send_mail() – https://docs.djangoproject.com/en/3.1/topics/email/#send-mail

And in addition to message i specified html_message since it is "the classic example is to send both text and HTML versions of a message" – https://docs.djangoproject.com/en/3.1/topics/email/#sending-alternative-content-types

from django.core.mail import send_mail

subject = "test"
message = "text"
html_message = "<p>text<p> <img src='Object URL'/>" # Object URL is link to AWS S3 image like https://demo.s3.amazonaws.com/test.png
from_email = "from@t.com"
recipients_list = ["recipient@t.com"]
send_mail(subject, message, from_email, recipients_list, fail_silently=False, html_message)

Sending to more than one users recipients, personal sending, each user will see like he is only one whom mail was sent:

I used EmailMultiAlternatives – https://docs.djangoproject.com/en/3.1/topics/email/#sending-alternative-content-types

In addition get_connection() – https://docs.djangoproject.com/en/3.1/topics/email/#sending-multiple-emails

from django.core.mail import EmailMultiAlternatives, get_connection

subject = "test"
message = "text"
html_message = "<p>text<p> <img src='Object URL'/>" # Object URL is link to AWS S3 image like https://demo.s3.amazonaws.com/test.png
from_email = "from@t.com"
recipients_list = ["recipient1@t.com", "recipient2@t.com", "recipient3@t.com"]
email_messages = []
for recipient in recipients_list:
    msg = EmailMultiAlternatives(subject, message, from_email, [recipient])
    msg.attach_alternative(html_message, "text/html") # we need it to send html_message
    email_messages.append(msg)
connection = get_connection()
connection.send_messages(email_messages) # send_messages() opens a connection on the backend, sends the list of email_messages, and then closes the connection again

Please ask if you have any questions!

Leave a comment