Making Your Own Email Templates in Django

I’m looking to make my app notify the user when their object is updated by an admin.

Problem is that I had no idea how to use my own email templates.

But I got it working! Here’s how you can put it into your Django project.

I’m using Django 2.2.5.

Email settings in settings.py

There are two useful ways that I know of to send emails:

  1. Logging directly to the console window
  2. Sending via SMTP

The first is much quicker, but doesn’t actually send emails. It’s good for testing.

The second is what you need if you need to actually send emails to a user’s inbox.

Send to console

To log emails to the console, you just have to set EMAIL_BACKEND in settings.py.

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

Here’s the documentation for reference.

Send via SMTP

There are a few settings you’ll need to take care of if you want to send live SMTP emails:

  • EMAIL_BACKEND
  • EMAIL_HOST
  • EMAIL_PORT
  • EMAIL_HOST_USER
  • EMAIL_HOST_PASSWORD
  • EMAIL_USE_TLS or EMAIL_USE_SSL

If you’re ready to give this a try, I would recommend making an account on SendGrid. Even when your free trial expires, you’ll have 100 free emails to send every day. Not enough to run a big project, but plenty of leeway to learn how to use it.

Once you’re signed up, you want to find the “Send Your First Email” instructions to set up SMTP.

Under "Send Your First Email", click the Start button next to "Integrate using our Web API or SMTP relay"

Then choose SMTP relay, not Web API.

Click Choose under SMTP Relay

Then you need to create a SendGrid API key. This will be your EMAIL_HOST_PASSWORD in your settings.py file.

In step one, type in a name for your key and click the button "Create Key". In step two, you'll see listed settings. These will go into your settings.py file.

After naming and creating your key, step two will autopopulate. These are what you’ll use in your settings.py file. Here’s a link to the SendGrid documentation on exactly how to set it up.

Make sure all the settings outlined at the beginning of this section are set, then you can try to actually send an email.

django.core.mail.send_mail()

The send_mail() function has been in Django for a while now. It takes a few parameters and sends an email. Very simple.

You must supply:

  • subject: string
  • message: string
  • from_email: string
  • recipient_list: list of strings

Worth noting: message is the plain text body content of the email. If you want to send an HTML email, you want to set html_message. We’ll do both to get some practice with templates.

Here’s a link to the Django documentation for reference.

Sending a plain email

Let’s practice. I’m assuming you have your EMAIL_BACKEND all set up. If you can’t get SMTP working, make it log to console to get the templates working first. It’s much more encouraging than sitting there trying to troubleshoot why SMTP isn’t sending.

# settings.py
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

So for my app, I want to send an email when my UpdateView form is submitted. This will notify the user related to that model object that they should come check it out. So I will override the form_valid() method to send an email when the UpdateView form is submitted.

# app_name/views.py
from django.core.mail import send_mail
from django.views.generic import UpdateView

from .models import ModelName

class ModelNameUpdateView(UpdateView):
  model = ModelName
  fields = ['field_1', 'field_2',]
  template_name = 'app_name/model_name_update.html'

  def form_valid(self, form):
    if request.method.POST:
      from_email = 'me@email.com'
      recipient_list = ['yourpersonalemail@email.com',] # put your real email here
      subject = 'Subject text'
      message = 'This is the body of the email.'
      send_mail(subject, message, from_email, recipient_list)
    return super(ModelNameUpdateView, self).form_valid(form)

Update your model with your UpdateView and check your console or inbox (depending on your EMAIL_BACKEND) to see if it worked!

django.template.loader.render_to_string()

Now that emails are working, we can discuss the templates. Email templates work just like your traditional Django templates that load pages: we put the content in its own file, then we point to that file.

Create templates

We’re going to make three templates:

  • app_name/email/model_name_update_email_subject.txt
  • app_name/email/model_name_update_email_message.txt
  • app_name/email/model_name_update_email_message.html

Make those three files in their respective folders, then move your strings from views.py to each respective template.

app_name/email/model_name_update_email_subject.txt

Subject text

app_name/email/model_name_update_email_message.txt

This is the body of the email.

app_name/email/model_name_update_email_message.html

This is the <strong>html body</strong> of the email.

Call render_to_string()

Now we move back to views.py where we want to point to these template files.

# app_name/views.py
from django.template.loader import render_to_string # new
from django.core.mail import send_mail
from django.views.generic import UpdateView

from .models import ModelName

class ModelNameUpdateView(UpdateView):
  model = ModelName
  fields = ['field_1', 'field_2',]
  template_name = 'app_name/model_name_update.html'

  def form_valid(self, form):
    if request.method.POST:
      from_email = 'me@email.com'
      recipient_list = ['yourpersonalemail@email.com',] # put your real email here
      subject = render_to_string(
        template_name='app_name/email/model_name_update_email_subject.txt'
      )
      message = render_to_string(
        template_name='app_name/email/model_name_update_email_message.txt'
      )
      html_message = render_to_string(
        template_name='app_name/email/model_name_update_email_message.html'
      )
      send_mail(subject, message, from_email, recipient_list)
    return super(ModelNameUpdateView, self).form_valid(form)

render_to_string() is kind of like the render() function you may have used before, but returns the template as a string instead of an HttpResponse object.

Give that a shot and see if it works… hint: it probably won’t.

Python strip() function

I got a variety of errors here.

  • When using the console backend, I just didn’t see any email at all.
  • When using the SMTP backend, I received a BadHeaderError that says “Header values can’t contain newlines”.

You might be thinking “I don’t want any newlines in there!”

Well, we have a solution: strip the whitespace off the ends with the python strip() function. Update your views.py file…

# app_name/views.py
subject = render_to_string(
  template_name='app_name/email/model_name_update_email_subject.txt'
).strip() # new

And try to trigger your email again.

And it should be successful!

Leave a Reply