Riding high with django-pony-express

How and why class-based emails will save your day

Ronny Vedrilla
ambient-digital
5 min readJun 1, 2023

--

After working on more than 20 business applications with Pythons famous web-framework “Django” since 2011, I felt the strong urge of not wanting to implement emails over and over again.

The struggle is always the same:

  • Writing duplicated templates for HTML and plain-text parts (Which is not very DRY).
  • Having to define variables for the base email template per email implementation instead of having it wrapped up in a single place.
  • Adding convenience features like a “Reply-To” header every time I implement a new email.

Luckily, I already fixed the issue of not being able to properly (unit-)test my emails with my test suite, described in my blog post from 2019.

If you are just looking for a solution, scroll to the TL;DR section at the end — noone will blame you 🙃

Photo by Thanuj Mathew on Unsplash

Motivation

In most of todays business (or fun) applications, you’ll want to send emails to your users. Whether to confirm a booking or to notify that something of significance had happened — you’ll end up sending emails.

Even though newer in-app notification mechanisms (like the “Facebook notification bell”) become more and more popular, you probably still have to send emails to inform your user that they have unread notifications within your application.

Emails are perceived as a reliable way to have something concrete, like a letter, in the analog world and could also be legally binding.

Long story short: You won’t get rid of them any time soon.

Solution

Preventing Abominations

Similar to Djangos class-based views, we implemented a pattern called “class-based emails”.

Instead of (re-)writing low-level email functions over and over again, our package named django-pony-express will encapsulate all the gritty details and let you focus on what matters: Writing your business logic.

How we want emails NOT to be

Instead of having an abomination per email implementation, as depicted in the above image, we can create a base class for the project to set global variables and some constant values (see picture below).

Defining a project-wide base class

Let’s have a detailed look at what’s going on here:

  • We define a subject prefix which will be prepended to any email subject. This will make all emails from your site look consistent in the users email inbox and makes it easy to search for.
  • We set the “Reply-To” header to a constant.
    This is a good idea because your emails will be sent by some bot — usually sent from noreply@mydomain.com. If a user tries to reply to this email address, they won’t get far. If you instead send from “info@mydomain.com”, you suggest that the email was sent by a person. The reply-to-header solves this problem neatly.
  • We define a bunch of variables needed for our base email template.
A base class for your “BeerBearInc” project. It inherits from our “BaseEmailService”

Implementing a new email

It is now easy and straight-forward to implement a new email instance.
Just set a couple of class attributes.

Example for a class-based email

The benefits are obvious:

  • No code duplication
  • Not worrying about how to actually send an email
  • No duplicated templates
  • Less code
  • Super fast creation of new emails
  • Neat 🤓

Features

The package ships with an abundance of features on which you can read up on over at ReadTheDocs. Nevertheless, we’ll present a few of them here.

Autogeneration of “plain-text” content

Every email must have a “plain text” content — mostly for historical and preview reasons. Maintaining two templates (HTML & plain-text) per email is not DRY, tedious and error-prone.
If you don’t provide a plain-text template, the package will automatically render it for you — based on your HTML template.

Internationalisation

The emails will automatically try to detect the currently relevant language by using Django settings.
If there is no language found, this feature is automatically disabled.

Attachments

You can easily add attachments to your emails if your use-case requires it.

Logging

Add or customise logging in your custom base class to save your future you by having unified and detailed logging.

Factories

Imagine you want to send the same email to a number of users but want to have an individual salutation.
Go for a factory which will dynamically create instances of class-based emails for you — exactly the way you need it.

Test suite

As stated at the beginning, this package ships a fully-fledged test suite to properly unit-test your emails. Refer to the documentation on how to awesomely work with it.

Derive and conquer

Keep in mind that the structure of the class-based emails is similar to Djangos class-based views — this means you can overwrite all functionality if you need to do so. If not, everything should work out-of-the-box.

Example of how to customise the way the emails find out which language is supposed to be chosen

TL;DR

You just need to get going quickly? No worries, just follow these steps:

  • Install the django-pony-express package from PyPI
  • Create your own BaseEmailService and configure it to suit your projects needs
  • Implement n neat class-based emails 🚀

Summary

Why would you want to use the django-pony-express?

Apart from the improved structure similar to Django views, which makes it easy to learn, you reduce the time you need to implement emails. Furthermore, you’ll finally get a DRY setup without having to invent a method of your own.

Have fun delivering emails even faster with the django pony express! 🐎

--

--

Ronny Vedrilla
ambient-digital

Tech Evangelist and Senior Developer at Ambient in Cologne, Germany.