Guide 2023 By davegaeddert

Sending email with Django templates

Django templates are a powerful, familiar way to render HTML in Django projects. You can use them for your emails too!

Here's an example class:

from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.template.loader import TemplateDoesNotExist, render_to_string
from django.utils.html import strip_tags


class TemplateEmail:
    def __init__(
        self,
        to,
        subject,
        template,
        context={},
        from_email=None,
        reply_to=None,
        **email_kwargs,
    ):
        self.to = to
        self.subject = subject
        self.template = template
        self.context = context
        self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
        self.reply_to = reply_to

        self.context["template"] = template

        self.html_content, self.plain_content = self.render_content()

        self.to = self.to if not isinstance(self.to, str) else [self.to]

        if self.reply_to:
            self.reply_to = (
                self.reply_to if not isinstance(self.reply_to, str) else [self.reply_to]
            )

        self.django_email = EmailMultiAlternatives(
            subject=self.subject,
            body=self.plain_content,
            from_email=self.from_email,
            to=self.to,
            reply_to=self.reply_to,
            **email_kwargs,
        )
        self.django_email.attach_alternative(self.html_content, "text/html")

    def render_content(self):
        html_content = self.render_html()

        try:
            plain_content = self.render_plain()
        except TemplateDoesNotExist:
            plain_content = strip_tags(html_content)

        return html_content, plain_content

    def render_plain(self):
        return render_to_string(self.get_plain_template_name(), self.context)

    def render_html(self):
        return render_to_string(self.get_html_template_name(), self.context)

    def get_plain_template_name(self):
        return f"email/{self.template}.txt"

    def get_html_template_name(self):
        return f"email/{self.template}.html"

    def send(self, **send_kwargs):
        return self.django_email.send(**send_kwargs)

You can then place emails in templates/email/{template}.txt and templates/email/{template}.html. If you only include a .html template, the plain text version will be rendered for you with Django's strip_tags.

To send an email, you can do something like this:

template = TemplateEmail(
    to=self.email,
    subject=f"Daily news",
    template="daily",
    context={"news": news_items},
)
template.send()

Feedback

Questions or ideas for improvement? Open a GitHub Discussion →