Sending HTML emails with embedded images from Django

Currently I'm working on an application which sends HTML emails with embedded or inline images and multiple CSV and PDF attachments.

Let's assume that we will be using an object containing data for our email. I'm providing my models here just as a reference to get the idea:

class RenderedReport(models.Model):  
    report = models.ForeignKey('Report', related_name='rendered_reports')

    approved_by = models.ForeignKey('auth.User', blank=True, null=True)

    date_rendered = models.DateTimeField(auto_now_add=True)
    date_queried = models.DateTimeField(blank=True, null=True)
    date_approved = models.DateTimeField(blank=True, null=True)
    date_sent = models.DateTimeField(blank=True, null=True)

    class Meta:
        get_latest_by = 'date_rendered'
        ordering = ('-date_rendered',)

    def __unicode__(self):
        return str(self.report)


class RenderedView(models.Model):  
    rendered_report = models.ForeignKey('RenderedReport', related_name='rendered_views')
    view = models.ForeignKey('View', related_name='rendered_views')

    png = ImageField(upload_to='reports')
    pdf = models.FileField(upload_to='reports', blank=True)
    csv = models.FileField(upload_to='reports', blank=True)

    class Meta:
        ordering = ['view']

    def __unicode__(self):
        return str(self.view)

    @property
    def image_filename(self):
        return os.path.basename(self.png.name)

I don't like the idea of re-inventing the wheel, so I will be using a responsive email template from Zurb Studios.

I'm skipping the entire HTML template code for brevity because it wasn't modified. We only need this part:

<!-- BODY -->  
<table class="body-wrap">  
    <tr>
        <td></td>
        <td class="container" bgcolor="#FFFFFF">
             <div class="content">
                <table>
                    <tr>
                        <td>
                            {% for view in views %}
                                <h3>{{ view }}</h3>
                                <p><img src="cid:{{ view.image_filename }}" /></p>
                                {% if not forloop.last %}<p>&nbsp;</p>{% endif %}
                            {% endfor %}
                        </td>
                    </tr>
                </table>
            </div>
        </td>
        <td></td>
    </tr>
</table><!-- /BODY -->  

Now it's time to get our HTML email rendered. We won't be sending a plain-text version of our email,

I'm providing a simplified, but a working snippet of Python code:

import os  
from email.mime.image import MIMEImage

from django.core.mail import EmailMultiAlternatives  
from django.template.loader import render_to_string


rendered_report = RenderedReport.objects.get(pk=1)  
views = rendered_report.rendered_views.all()

context = {'views': views}

html_content = render_to_string('reports/email.html', context=context).strip()

subject = 'HTML Email'  
recipients = ['john.doe@test.com']  
reply_to = ['noreply@test.com']

msg = EmailMultiAlternatives(subject, html_content, config.formatted_email_from, to, reply_to=reply_to)  
msg.content_subtype = 'html'  # Main content is text/html  
msg.mixed_subtype = 'related'  # This is critical, otherwise images will be displayed as attachments!

for view in views:  
    # Create an inline attachment
    image = MIMEImage(view.png.read())
    image.add_header('Content-ID', '<{}>'.format(view.image_filename))
    msg.attach(image)

    # Create a regular attachment with a CSV file
    if view.csv:
        filename = os.path.basename(view.csv.name)
        msg.attach(filename, view.csv.read(), 'text/csv')

    # Create a regular attachment with a PDF file
    if view.pdf:
        filename = os.path.basename(view.pdf.name)
        msg.attach(filename, view.pdf.read(), 'application/pdf')

msg.send()  

This will send a responsive HTML email containing inline images and attachments.

Please pay additional attention to the line with msg.mixed_subtype = 'related'. It sets the email header Content-Type: multipart/related; guaranteeing that your images will be displayed inline and not as attachments.

I'm providing an example how the <img> tag will be rendered: <img src="cid:20161010_dailykpisnapshot_OCuZ4O4.png">

And here's the email headers:

Content-Type: image/png  
Content-Disposition: inline  
Content-Transfer-Encoding: base64  
Content-ID: <20161010_dailykpisnapshot_OCuZ4O4.png>  

Michael Samoylov

Python, JavaScript and Swift Expert with 12+ years of experience.

Vilnius, Lithuania https://monmar.tech

Subscribe to Michael Samoylov

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!