Easy Multi-Part E-Mails with Django

Posted by Ross Poulton on Thu 25 October 2007 #geeky #email #django #programming

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!