New take on old ticket "Send templated email"

I am +1 on adding “Using Django templates for emails” to Django’s docs, +1 on eventually adding some sort of class-based email construction API (after prototyping and discussion), and -1 on adding any new template email helper APIs at this point.

Here’s @sarahboyce’s example rewritten with the render_to_string() template helper and new text_body and html_body options I’ve proposed in another thread:

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

def send_example_email_using_templates():
    context = {
        "recipient": "Jane",
        "confirm_email_link": "https://confirm_link.com",
        "report_link": "https://report_link.com",
    }

    email = EmailMessage(
        subject=render_to_string("email_subject.txt", context).trim(),
        text_body=render_to_string("email.txt", context),
        html_body=render_to_string("email.html", context),
        from_email="from@test.com",
        to=["example@gmail.com"],
    )
    email.send()

I feel like this is not a lot of code, and that some version of it would be a useful example in the docs. (Even with added EmailMultiAlternatives boilerplate, if text/html_body gets rejected.) I also feel it’s not really enough code or complexity to warrant a new helper function in Django core.

Did you notice I snuck an extra feature into Sarah’s code? It’s also rendering the email subject from a template. I borrowed that idea from (I think) one of the old Pinax projects, but you’ll also find it in django-allauth’s email templates, django-postoffice, and many other places. I’ve found subject templates simplify my code; others might argue they’re unnecessary complexity.

And that gets at why I’m -1 on adding a new templated email helper. I don’t think we’re going to identify a one-size-fits-all—or even one-size-fits-most—approach. Here are some other examples that come up pretty regularly in practice:

  • Do you want separate text and html templates? Or just html and generate the plaintext from that, per @GitRon’s original suggestion top of thread? Or just text and generate the html, like Django eMark? Perhaps you’d prefer a single template with separate blocks for the subject, text, and html, like django-templated-email? (Or maybe you just want an html-only or text-only message?)
  • If your html uses CSS styles, you’ll probably want to post-process it with Premailer, after rendering before sending. (Because it’s no fun to maintain templates with the CSS already inlined. It’s already messy enough with email still needing table-based layout!)
  • For text body (and subject), you probably want to post-process the rendered message to squash excess newlines and other artifacts of template rendering— &‍amp; you might want to disable autoescape.&‍#28517;

Could we pick some subset for an opinionated send_template_email() helper? Maybe. But if it doesn’t support the kinds of things people need to do in real projects, we’ve just added more complexity and maintenance burden to django.core.mail while still not really making it “fit for use.” And we end up with the same sort of awkward split we have between send_mail() and EmailMessage/​EmailMultiAlternatives, where the simple helper is often insufficient, but the full-featured alternative requires a lot of knowledge to use.

That’s why I think the class-based email idea has promise: a set of well designed classes could support several common cases, while also providing extension points that allow (and make it obvious where to implement) things like post-processing and converting between content types. There’s a lot of prior art in this area: in addition to django-ponyexpress, take a look at django-yubin, django-mailviews, and this TemplateEmail Forge Guide.

Until then, though, I think some how-to documentation on using templates to send email would be really helpful.

[Incidentally—and probably not surprisingly—many of the questions and positions from this thread came up in the original ticket #17193.]

2 Likes