Tying up a loose end -How class-based emails will save your day
Published October 8, 2023
This video features Ronny Vedrilla at DjangoCon Europe 2024 in Vigo, Spain.
Talk: Tying up a loose end - How class-based emails will save your day by Ronny Vedrilla
https://pretalx.evolutio.pt/djangocon-europe-2024/talk/YXAWAD/
Ronny Vedrilla presents Django Pony Express, a class-based email service intended to remove repetitive and error-prone work from sending emails in Django applications. A project-specific base email class can centralize templates, context such as logos and unsubscribe links, headers, reply-to handling, subject prefixes, validation, error handling, logging, attachments, and HTML-to-plain-text conversion, while individual emails require only a small amount of code. The package also provides optional threaded sending, factories, and an email test service that makes it easier to query Django’s test outbox and verify recipients, subjects, headers, and content in both HTML and plain-text parts. He argues that the abstraction remains useful even for developers who dislike class-based views, because it packages functionality that would otherwise be repeatedly implemented, and that the open-source code can be maintained or copied into a project if necessary.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Speaker 1: Okay, give him a big hand everybody. Ronnie.
Speaker 2: Hi. Okay. Um yeah, hi, my name's Ronnie um and today I want to go to the first one. I want to talk about a pack I've built um in the last uh year, last two years, and it's called Django Pony Express and I hope I can convince you why why it's uh a good idea to use it. Um well, this is the agenda. Um I'm going to do a brief introduction, um, try to motivate why this email thing is important to me. Then we have an extensive history lesson, just kidding. Um, then I talk about the status quo in Django, um, then I present a solution. Okay, um that's me in front of a castle. I like history stuff. And um
Speaker 2: I study business information systems. Um I've been working with Django since uh twenty twelve, which is version one point four, the older might remember. Um I'm an individual member of the Django Software Foundation and I host the Django Meetup Cologne, which is one of the few uh Django meetups to survive COVID, which is really sad. And um yeah if you want to Well communicate with me. Um LinkedIn is the only social media I have, so uh find me. I think I'm the only person with that name there. Um I work for a company called Ambient. Um we're a full service agency, we do web and apps and If we can choose, we use Django and they give me the opportunity to be here, so I'm very thankful for them to them. So, emails.
Speaker 2: Um this is a question that I thought a lot of times and I've heard from other people people working with Django. Um it's not so much fun working with emails, especially if you build larger applications, um which we basically do. Business applications, trying to uh build small ELP systems, and you will have usually um many emails there. Um especially if you have n customers on your email then um on your platform then you will need even more emails. And um sure somebody might say yeah that but you know in ept notifications or things like this. But in the end how do you inform your users that you have in-ept notifications? Well, you send an email, so back to square one. So um yeah, I guess um we can agree that um emails are an integral part of most jungle applications, especially business applications.
Speaker 2: I think uh in the talk from J Yesterday um about the background workers we also saw stuff about emails. So um yeah. Um I've been working um with maybe twenty-five business applications, built twenty-five business applications in my years uh And um these are the the biggest pain points. At first that you have the not dry HTML part and plaintext part, which is kind of annoying and very un-jango-esque that you have to worry about this yourself. Um then usually emails would have like a Template within your CI with some logos, some unsubscribed links, so on. And you would need variables for this. And you would have to add them in every single email. And again, it's not dry. I usually tend to forget it.
Speaker 2: And it's tedious. Mm then working with emails kind of feels like it is an external API. Spoiler, it is an external API, at least the dispatching. Um but uh for example they won't really unit test it because it's annoying, it's possible, but you know If it's not convenient, probably you won't do it. And um like in one project maybe I would start adding convenience features, like you know, some headers or stuff like this, an unsubscribe link, like a good pattern for this. And in the next project I would have to write myself, oh yeah I already did this and copied over and this is not the way I like to work. So I set the package called Django Pony Express and uh we all know the great Django Pony here. And uh the Pony
Speaker 2: Express was an Mailing service, like an analog mailing service in the eighteen sixties in the US. It was super fast. Unfortunately it was superseded only one year later by the telegraph. Nevertheless, it's about mailing and it's about ponies, so I thought the name is very fitting. All right. So if you look at the Django documentation and you if you look up emailing, then you basically get this. It works, it's fine, um but it's very simple, everything is set directly, there are no variables, there's no abstraction, and usually it doesn't Really match the the emails I have to build because they more look like this abomination. I can see that you can't read any of it, that's not a problem, because that's kind of the part. Um because what you what I have to do basically for every email, you have to set the
Speaker 2: from email, you have to set the two email, maybe even fetch the two from some user object or something. There would be internationalization. There would be um headers to set, there would be the two templates I would render for the HTML part, for the plaintext part. There would be error handling. would be logging, there would be actual sending of an email and in case it's an HTML email and usually this is a thing what we want to do, would be an uh email multi alternatives class, which is not so intuitive, I would say. So um yeah, lots of stuff going on. Um I always had a function, so I would basically copy it over and tend to forget things and again it's not very convenient. So this means a lot of redundant code. It's very easy to make mistakes, trust me.
Speaker 2: Um we have the duplicate HTML plaintext part. It's uh the not so it the felt not so uh encapsulated email API from Django. Uh you can't really inherit anything, and every log message would look a little bit different, maybe if we email Email would look a tiny bit different because I would build it per on a per email basis. So if you want to do some logging aggregation or something, it would be very tedious to actually find okay, this is all the email stuff. Okay, so My solution or our solution is the Pony Express. And as you have maybe guessed from the name of the talk, it's class based. So after introducing the concept, many of my colleagues said yeah that's actually quite neat.
Speaker 2: So what does this mean? Um probably you all know the class based views in comparison to the function based views, and the idea is that you have kind of an abstraction um which does the regular case. For you. And if you need something, then you can override specific methods and customize what you need to customize. But if you don't want to customize anything, you're just good to go with very few lines of code. So there's a base email service class and And if you extend from this, then you basically get all the features I've shown in this very huge block of code before, basically included in this. You don't have to worry about this anymore. It works out of the box, it's very easy to stay dry, and obviously docs are. didn't happen. Uh everything is documented
Speaker 2: with the docs. Um this QR code will um um will guide you to Pypei Pypei if you're interested, um but you can also uh Google for it. Um yeah, so class-based emails. Um this is an example. Um probably maybe I don't know if you can read it. The thing is, it's a very small thing and it's just you extend from a class. Um you have the subject there, you define And um that's it basically. You don't have to worry about code duplication, don't have to worry about how to actually send the email or actively think about do I want an uh email multipath alternative or not. Um you don't have to To have duplicated templates anymore. You can if you want, but usually you don't care.
Speaker 2: It's weaeless code, it's testable. This is I think the key feature, but I'll come to this in a second. Um it's very fast to create new emails, it's neat, we're nerds, so we not like neat things. And um yeah, I'm not sure if you can read it, but here, as I said in the slide before, um I talked about base email service. This is a custom base email service, and this is basically how you um adapt the this generic package to your project. So when you um when you start a new project and you um you install the PonyExpress, what you need to do is you create a new email base class specific for your project. This means you would just extend from the base email service. And then for example you can set in the getContext data method you might uh might look familiar to you from the views.
Speaker 2: Then you can just define all the all the variables you need for your base template. For example, the unsubscribed link or you know where your logo lives or something like that. To have a little bit new content for the people who have seen me last year at the Django Day in Copenhagen, which was really great, um I added a tiny bit of async support because you shouldn't send emails in the main thread and I tried to basically um find a solution that you don't have to think about anything anymore. So by default the Pony Express would send emails in the main thread, which is not good. So I started um new base classes. It would be a threat email service. It's very easy. I'm not really sure if this is um the best thing for extensive production use, but um if you take the pattern it's very easy to create something for
Speaker 2: um salary, jungle q or maybe even the background workers. So I' very uh very eager to to see where this goes and if they come in I promise I will add the functionality to this. Um yes so um here are some features which are um included in the in the Express. It's really sad that it's really impossible to see anything from the code, but yeah, well, happens. If you want, um you can customize it by overwriting the get translation method, quite obvious. Um then um there's another thing I would I um named the reply to
Speaker 2: dilemma, is that usually a um you send your emails from uh no reply at your domain. Um but sometimes people want to reply to this because maybe there's an error, a bug or they think hey why do I get this email? I didn't I didn't sign up for it. So um what you can What you can do is you can set the reply to address header. And if somebody replies to this, then um it's the it the the email client automatically takes this address instead of the sender, which is kind of neat. And you still can communicate, hey, this email was Sent by a human if you look at the no reply. And uh yeah, you can just set this in your base class and then it will be automatically set. You don't have to worry about any of the of the details of how to actually do it.
Speaker 2: Um yeah, some additional candy. Um there's an under the hood HTML text conversion, so you don't need the HTML uh the uh the plain text email, uh the plain text template, sorry. And um yeah if you want to override this, if you care about the plain text for example, if um for example Some email clients use the plain text as a preview if it pops up and if you really care about that everything looked great then you shouldn't use this because it m kind of makes some odd things. But if you don't care and just want it to work in case somebody, you know, has disabled HTML emers, you can just use it. Um there is a subject prefix which helps you to um for example you have a service called my project, whatever. Then you can prefix all your emails, for example, account created, new order received.
Speaker 2: Account deleted, you can prefix with your for example my project thing. So if the customer it has a similar look and feel for all your emails, the customer needs email client can can you search for all the emails, group the emails, etc. Um you can add some custom validation um in your in your custom email class if you need it, for example to check hey, only active users should get this email or add some some email specific business logic if you want. Um there 's Some uh error handling you can c um uh you can customize and uh I have factories. Um these factories imagine for example you want to send all to all your managers some monthly report, but they all have the email itself is the same but The content is a tiny bit different.
Speaker 2: Then you can use the factories. It's a concept that's in in Django. It's a little bit difficult and uh it's kind of an edge case, I would feel. But nevertheless, it's in there so you can use it. Um there would be some basic logging. And uh I added a switch there because sometimes in logging it's quite imp quite important to know who didn't get the email if you know there's an error or something. But obviously you don't want to leak personal data into some logging service. So uh privacy by design, it's disabled, but if you know what You're doing you can enable it so it will log by default the email address and the error, obviously. Um yeah, and you can use attachments, but this is not something you would expect, I would guess. Okay. So So this I think is the
Speaker 2: USB feature of this. Um it's not really connected to the to the class based emails and it could be a standalone package. Um I actually thought about if I should split this out, but for now I think it's um It's valid that it lives inside this package. And um yeah, what happens if you want to test emails in your unit test? When your code triggers um c when your code triggers an email, then the the Django test framework if you use Django test framework, it will add this to the um to a mail dot outbox list in in in the end. And um if you want to get the HTML part of the first email in this outbox, you would need to do like Mail. outbox on position zero.
Speaker 2: alternatives on position zero on position zero and then you can access the HTML part. It's a little bit clunky, right? So um what there is now an an email test service and this test service is In practice just a wrapper for this list. But um there are lots of methods which makes you interact with it like a Django query set. You can filter by subject, you can filter by recipient, you can filter by some other headers, right? count how many emails are in there. And um a very typical example would be that you say, um okay, I want to test to to see if um you know my code sends the correct emails. So you check how many emails are in the um in the outbox. So you You use this uh email test service and you count it.
Speaker 2: Then you trigger your code to see, okay, I want to to trigger the email. Then you check okay, is there one email or maybe five, depending on your use case, more in the outbox. Okay, great. Then you check is the subject correct? And Then you have to check obviously the content, the recipient, maybe the from, depends on what you're doing. And the most important part obviously is the content. Again, even though you don't have to set explicitly an um an um plaintext template, it's still there because it's part of the email protocol how it works. So there's a a helper. It's called uh a third body contains and it will check for you in both parts so you don't forget, you know, to update one part. This is uh in this happens a lot in reality. So um Yeah. There's even more stuff this thing can do.
Speaker 2: Just have a look at the documentation if you're interested, or ask me. And um yeah, Outlook. Um as I said, uh this um talk about the background workers yesterday was very inspiring and I'm really hoping to uh to see them soon. And um yeah, I think this would be a very, very neat um addition to solve this um async problem. Um maybe provide some um additional async base classes for salary Django Q and so on. Um I think I will go by demand on this one. And um yeah maybe Maybe uh write a depth to get this thing uh in Django Cork.
Speaker 2: Okay, so um critical review. Um there's There are lots of voices um against class based views. So some people say, Okay, this all looks great, but why should we put our money on class based emails if class based views somehow are maybe discouraged? Well, in the end I built lots of things which are not there. So if you don't use the package, then you would have to build it by yourself. Um and there is no function-based alternative, like this convenient. So I would say it's still worth it, even if you're no fan of cloud-based use. Um then And some people would say, Hey, you know, I'm you know, have hundreds of emails in my project and I built them all the Pony Express. What if it's um gets discontinued at some point? Well as always you can always connect on uh on GitHub, it's open source
Speaker 2: obviously. Um Um you can always uh w write to me, I'm the maintainer. And in the worst case that you know, everybody is uh was hit by a bus and nobody's there, you can still just copy the code over in your project and maintain it from there. It's still easier than writing everything by yourself, trust me. Okay, and last but not least, um if this really ties up a loose end in Django , shouldn't Django provide a way to deal with this? Maybe, but it doesn't. So So I do, thank you.
Django Pony Express provides class-based email services with defaults for rendering, headers, error handling, logging, and sending. You subclass a base service and override only what is specific to each email, making new emails shorter, more consistent, and easier to test.
Discussed at 6:17It includes a thread-based email service so sending does not have to happen in the main thread, though the speaker is cautious about using that approach extensively in production. The same pattern could be adapted to Celery, Django-Q, or other background-worker systems.
Discussed at 8:36Pony Express can convert the HTML body to plain text automatically, so a separate plain-text template is not required. The speaker recommends overriding that behavior when the plain-text preview needs careful formatting.
Discussed at 10:56Pony Express’s email test service wraps Django’s outbox with query-like methods, letting you filter and count messages by subject, recipient, or headers. Helpers can also check message content in both the HTML and plain-text parts.
Discussed at 14:02Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025