Easy Multi-Part E-Mails with Django

Every time I send e-mail with Django I seem to do it slightly differently, especially when it comes to sending HTML-based emails.

So, I did what any good Djangonaut does and wrote a quick function that wraps up sending of a multi-part HTML / Text e-mail from templates you create.

Firstly, create your e-mail templates. You'll need both a HTML and a plain-text template; for the HTML template I strongly recommend the use of Premailer, which puts all CSS definitions inline.

Here's a sample HTML template - as it would come out of Premailer. We'll pretend it's in the emails template directory, with a name of newsite.html.

<h1 style='font: Arial; font-weight: bold; font-size: 15pt; color: #006;'>Thanks!</h1>

<p style='font: Arial,Helvetica; size: 10pt;'>Hello, {{ username }}.</p>

<p style='font: Arial,Helvetica; size: 10pt;'>Thank you for submitting your website, titled <strong>{{ title }}</strong>, to our listing.</p>

<p style='font: Arial,Helvetica; size: 10pt;'>We'll verify it, take a screenshot, and publish it within the next few days.</p>

<p style='font: Arial,Helvetica; size: 10pt;'>Regards,</p>

<p style='font: Arial,Helvetica; size: 10pt;'><em>Your Favourite Webmaster.</em></p>

And here's that same e-mail, in plain-text format - also in the e-mails folder, called newsite.txt.

Hello, {{ username }}.

Thank you for submitting your website, titled '{{ title }}', to our listing.

We'll verify it, take a screenshot, and publish it within the next few days.


Your Favourite Webmaster.

Those who are familiar with Django's template language will see that this template uses two variables, username and title. Let's populate them, shall we?

template_context = {
    'username': 'joecitizen',
    'title': 'My Movie Review Site',
    'url': 'http://www.moviewreviews.dom',

So far we have an e-mail message template in both HTML and text formats; all we need now to send an e-mail is a subject and a recipient.

recipient = 'joe@example.dom' # ['joe@example.dom',] would also be suitable
subject = 'Site submission: {{ url }}'

That's pretty straightforward - our subject also contains Django template variables, in this case the URL from our template context we created earlier.

Now it's time to send the message. I have been using this function with much success:

def send_multipart_mail(template_name, email_context, subject, recipients, sender=None, fail_silently=False):
    This function will send a multi-part e-mail with both HTML and
    Text parts.

    template_name must NOT contain an extension. Both HTML (.html) and TEXT
        (.txt) versions must exist, eg 'emails/public_submit' will use both
        public_submit.html and public_submit.txt.

    email_context should be a plain python dictionary. It is applied against
        both the email messages (templates) & the subject.

    subject can be plain text or a Django template string, eg:
        New Job: {{ job.id }} {{ job.title }}

    recipients can be either a string, eg 'a@b.com' or a list, eg:
        ['a@b.com', 'c@d.com']. Type conversion is done if needed.

    sender can be an e-mail, 'Name <email>' or None. If unspecified, the
        DEFAULT_FROM_EMAIL will be used.

    from django.core.mail import EmailMultiAlternatives
    from django.template import loader, Context
    from django.conf import settings

    if not sender:
        sender = settings.DEFAULT_FROM_EMAIL

    context = Context(email_context)

    text_part = loader.get_template('%s.txt' % template_name).render(context)
    html_part = loader.get_template('%s.html' % template_name).render(context)
    subject_part = loader.get_template_from_string(subject).render(context)

    if type(recipients) != list:
        recipients = [recipients,]

    msg = EmailMultiAlternatives(subject_part, text_part, sender, recipients)
    msg.attach_alternative(html_part, "text/html")
    return msg.send(fail_silently)

Lastly, to put together the previous example:

from multipart_email import send_multipart_email # Assuming, of course, you saved the above functoin in multipart_email.py

send_multipart_mail('emails/newsite', template_context, subject, recipient)

What this does, is uses our existing template context, subject, and recipient, to send a HTML and plain-text e-mail based off the templates we built earlier. Note that it automatically appends .html or .txt as required. Hopefully the extra documentation in the docstring is useful.

Recipients who can read HTML e-mail will see your nicely formatted HTML e-mail message, while everyone else will see the plain-text alternative.

Slowly, this method is finding it's way into each of my Django applications. Please, just don't overdo the HTML messages!

Thanks for reading.

I'd love to hear your feedback and comments. E-mail me, ross@rossp.org, or get in touch on Twitter where I'm @RossPoulton. Chat soon!