Send, Receive, and Test Emails in Django

On September 16, 2022
14min read
Dmitriy Shcherbakan Full Stack Developer @Railsware

Some time ago, we discovered how to send an email with Python using smtplib, a built-in email module. Back then, the focus was made on the delivery of different types of messages via SMTP server. Today, we prepared a similar tutorial but for Django.

This popular Python web framework allows you to accelerate email delivery and make it much easier. And these code samples of sending emails with Django are going to prove that. 

A simple code example of how to send an email

Let’s start our tutorial with a few lines of code that show you how simple it is to send an email in Django. 

Import send_mail at the beginning of the file:

from django.core.mail import send_mail

And call the code below in the necessary place.

send_mail(
    'That’s your subject',
    'That’s your message body',
    'from@yourdjangoapp.com',
    ['to@yourbestuser.com'],
    fail_silently=False,
)

These lines are enclosed in the django.core.mail module that is based on smtplib. The message delivery is carried out via SMTP host, and all the settings are set by default:

EMAIL_HOST:  'localhost'
EMAIL_PORT: 25
EMAIL_HOST_USER: (Empty string)
EMAIL_HOST_PASSWORD: (Empty string)
EMAIL_USE_TLS: False
EMAIL_USE_SSL: False

Note that the character set of emails sent with django.core.mail are automatically set to the value of your DEFAULT_CHARSET setting.

You can learn about the other default values here. Most likely, you will need to adjust them. Therefore, let’s tweak the settings.py file.

Setting up

Before actually sending your email, you need to set up for it. So, let’s add some lines to the settings.py file of your Django app.

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.yourserver.com'
EMAIL_PORT = '<your-server-port>'
EMAIL_HOST_USER = 'your@djangoapp.com'
EMAIL_HOST_PASSWORD = 'your-email account-password'
EMAIL_USE_TLS = True
EMAIL_USE_SSL = False

EMAIL_HOST is different for each email provider you use. For example, if you have a Gmail account and use their SMTP server, you’ll have EMAIL_HOST = ‘smtp.gmail.com’.

Also, validate other values that are relevant to your email server. Eventually, you need to choose the way to encrypt the mail and protect your user account by setting the variable EMAIL_USE_TLS or EMAIL_USE_SSL.

If you have an email provider that explicitly tells you which option to use, then it’s clear. Otherwise, you may try different combinations using True and False operators. Note that only one of these options can be set to True.

EMAIL_BACKEND tells Django which custom or predefined email backend will work with.

EMAIL_HOST. You can set up this parameter as well.

SMTP email backend 

In the example above, EMAIL_BACKEND is specified as:

django.core.mail.backends.smtp.EmailBackend.

It is the default configuration that uses SMTP server for email delivery. Defined email settings will be passed as matching arguments to EmailBackend.

host: EMAIL_HOST
port: EMAIL_PORT
username: EMAIL_HOST_USER
password: EMAIL_HOST_PASSWORD
use_tls: EMAIL_USE_TLS
use_ssl: EMAIL_USE_SSL

Unspecified arguments default to None

As well as django.core.mail.backends.smtp.EmailBackend, you can use:

  • django.core.mail.backends.console.EmailBackend – the console backend that composes the emails that will be sent to the standard output. Not intended for production use.
  • django.core.mail.backends.filebased.EmailBackend – the file backend that creates emails in the form of a new file per each new session opened on the backend. Not intended for production use.
  • django.core.mail.backends.locmem.EmailBackend – the in-memory backend that stores messages in the local memory cache of django.core.mail.outbox. Not intended for production use.
  • django.core.mail.backends.dummy.EmailBackend – the dummy cache backend that implements the cache interface and does nothing with your emails. Not intended for production use.
  • Any out-of-the-box backend for Amazon SES, Mailgun, SendGrid, and other services. 

How to send emails via SMTP 

Once you have that configured, all you need to do to send an email is to import the send_mail or send_mass_mail function from django.core.mail.  These functions differ in the connection they use for messages. send_mail uses a separate connection for each message. send_mass_mail opens a single connection to the mail server and is mostly intended to handle mass emailing. 

Sending email with send_mail

This is the most basic function for email delivery in Django. It comprises four obligatory parameters to be specified: subject, message, from_email, and recipient_list

In addition to them, you can adjust the following:

  • auth_user: If EMAIL_HOST_USER has not been specified, or you want to override it, this username will be used to authenticate to the SMTP server. 
  • auth_password: If EMAIL_HOST_PASSWORD  has not been specified, this password will be used to authenticate to the SMTP server.
  • connection: The optional email backend you can use without tweaking EMAIL_BACKEND.
  • html_message: Lets you send multipart emails.
  • fail_silently: A boolean that controls how the backend should handle errors. If True – exceptions will be silently ignored. If Falsesmtplib.SMTPException will be raised. 

For example, it may look like this:

from django.core.mail import send_mail

send_mail(
    subject = 'That’s your subject'
    message = 'That’s your message body'
    from_email = ‘from@yourdjangoapp.com’
    recipient_list = ['to@yourbestuser.com',]
    auth_user = 'Login'
    auth_password = 'Password'
    fail_silently = False,
)

Other functions for email delivery include mail_admins and mail_managers. Both are shortcuts to send emails to the recipients predefined in ADMINS and MANAGERS settings respectively.

For them, you can specify such arguments as subject, message, fail_silently, connection, and html_message. The from_email argument is defined by the SERVER_EMAIL setting.

What is EmailMessage for? 

If the email backend handles the email sending, the EmailMessage class answers for the message creation. You’ll need it when some advanced features like BCC or an attachment are desirable. That’s how an initialized EmailMessage may look:

from django.core.mail import EmailMessage

email = EmailMessage(
    subject = 'That’s your subject',
    body = 'That’s your message body',
    from_email = 'from@yourdjangoapp.com',
    to = ['to@yourbestuser.com'],
    bcc = ['bcc@anotherbestuser.com'],
    reply_to = ['whoever@itmaybe.com'],
)

In addition to the EmailMessage objects you can see in the example, there are also other optional parameters:

  • connection: defines an email backend instance for multiple messages. 
  • attachments: specifies the attachment for the message.
  • headers: specifies extra headers like Message-ID or CC for the message. 
  • cc: specifies email addresses used in the “CC” header.

The methods you can use with the EmailMessage class are the following:

  • send: get the message sent.
  • message: composes a MIME object (django.core.mail.SafeMIMEText or django.core.mail.SafeMIMEMultipart).
  • recipients: returns a list of the recipients specified in all the attributes including to, cc, and bcc
  • attach: creates and adds a file attachment. It can be called with a MIMEBase instance or a triple of arguments consisting of filename, content, and mime type.
  • attach_file: creates an attachment using a file from a filesystem. We’ll talk about adding attachments a bit later.

How to send multiple emails

To deliver a message via SMTP, you need to open a connection and close it afterwards. This approach is quite awkward when you need to send multiple transactional emails.

Instead, it is better to create one connection and reuse it for all messages. This can be done with the send_messages method that the email backend API has.

Check out the following example:

from django.core import mail
connection = mail.get_connection()

email1 = mail.EmailMessage(
    'That’s your subject',
    'That’s your message body',
    'from@yourdjangoapp.com',
    ['to@yourbestuser1.com'],
    connection=connection,
)
email1.send()

email2 = mail.EmailMessage(
    'That’s your subject #2',
    'That’s your message body #2',
    'from@yourdjangoapp.com',
    ['to@yourbestuser2.com'],
)
email3 = mail.EmailMessage(
  'That’s your subject #3',
    'That’s your message body #3',
    'from@yourdjangoapp.com',
    ['to@yourbestuser3.com'],
)

 with mail.get_connection() as connection:
        connection.send_messages([email2, email3])

What you can see here is that the connection was opened for email1, and send_messages uses it to send emails #2 and #3. After that, you close the connection manually. 

How to send multiple emails with send_mass_mail

send_mass_mail is another option to use only one connection for sending different messages. 

message1 = ('That’s your subject #1',
 'That’s your message body #1',
 'from@yourdjangoapp.com',
 ['to@yourbestuser1.com', 'to@yourbestuser2.com'])
message2 = ('That’s your subject #2',
 'That’s your message body #2',
 'from@yourdjangoapp.com',
 ['to@yourbestuser2.com'])
message3 = ('That’s your subject #3',
 'That’s your message body #3',
 'from@yourdjangoapp.com',
 ['to@yourbestuser3.com'])
send_mass_mail((message1, message2, message3), fail_silently=False)

Each email message contains a datatuple made of subject, message, from_email, and recipient_list. Optionally, you can add other arguments that are the same as for send_mail.

How to send an HTML email

All versions starting from 1.7 let you send an email with HTML content using send_mail like this:

from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.utils.html import strip_tags

subject = 'That’s your subject' 
html_message = render_to_string('mail_template.html', {'message': 'Hello!'})
plain_message = strip_tags(html_message)
# Note that above we are reading content from an HTML file 
from_email = 'from@yourdjangoapp.com' 
to = 'to@yourbestuser.com' 

send_mail(subject, plain_message, from_email, [to], html_message=html_message)

Older versions users will have to mess about with EmailMessage and its subclass EmailMultiAlternatives. It lets you include different versions of the message body using the attach_alternative method. For example:

from django.core.mail import EmailMultiAlternatives
subject = 'That’s your subject'
from_email = 'from@yourdjangoapp.com>'
to = 'to@yourbestuser.com'
text_content = 'That’s your plain text.'
html_content = '<p>That’s <strong>the HTML part</strong></p>'
message = EmailMultiAlternatives(subject, text_content, from_email, [to])
message.attach_alternative(html_content, "text/html")
message.send()

How to send an email with attachments

In the EmailMessage section, we’ve already mentioned sending emails with attachments. This can be implemented using attach or attach_file methods.

The first one creates and adds a file attachment through three arguments – filename, content, and mime type.

The second method uses a file from a filesystem as an attachment. This is how each method would look like in practice:

   with open('Attachment.pdf') as file:
        message.attach('Attachment.pdf', file.read(), 'application/pdf')
    message.send()

or

message.attach_file('/documents/Attachment.pdf', 'application/pdf')

Custom email backend

You’re not limited to the abovementioned email backend options and can tailor your own. For this, you can use standard backends as a reference. Let’s say, you need to create a custom email backend with the SMTP_SSL connection support required to interact with Amazon SES. The default SMTP backend will be the reference. First, add a new email option to settings.py.

AWS_ACCESS_KEY_ID = 'your-aws-access-key-id'
AWS_SECRET_ACCESS_KEY = 'your-aws-secret-access-key'
AWS_REGION = 'your-aws-region'
EMAIL_BACKEND = 'your_project_name.email_backend.SesEmailBackend'

Make sure that you are allowed to send emails with Amazon SES using these AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (or an error message will tell you about it :D)

Then create a file your_project_name/email_backend.py with the following content: 

import boto3
from django.core.mail.backends.smtp import EmailBackend
from django.conf import settings
class SesEmailBackend(EmailBackend):
  def __init__(
    self,
    fail_silently=False,
    **kwargs
  ):
    super().__init__(fail_silently=fail_silently)
    self.connection = boto3.client(
      'ses',
      aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
      aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
      region_name=settings.AWS_REGION,
    )
  def send_messages(self, email_messages):
    for email_message in email_messages:
      self.connection.send_raw_email(
        Source=email_message.from_email,
        Destinations=email_message.recipients(),
        RawMessage={"Data": email_message.message().as_bytes(linesep="\r\n")}
      )

This is the minimum needed to send an email using SES. Surely you will need to add some error handling, input sanitization, retries etc. but this is out of our topic. 

You might see that we have imported boto3 in the beginning of the file. Don’t forget to install it using a command

pip install boto3

It’s not necessary to reinvent the wheel every time you need a custom email backend. You can find already existing libraries, or just receive SMTP credentials in your Amazon console and use the default email backend. It’s just about figuring out the best option for you and your project.

Sending emails using Mailtrap Email Sending

Before any talk about email testing, we have one more sending option to cover and that is Mailtrap Email Sending, comprised of a reliable and hassle-free email API and SMTP service.

With Mailtrap Email Sending, developers receive a stable working email infrastructure as well as full control over email deliverability. And with an email delivery time of ≈ 1 sec, this sending solution manages to reach customer inboxes just in time.

To start sending through Mailtrap Email Sending from your Django application, you need to first create a Mailtrap account.

Then, you proceed to set up and verify a domain which is a secure and effortless process described in detail in the Mailtrap knowledgebase.

With that out of the way, feel free to go back to your Django app and install Mailtrap’s official Python client using the command below:

pip install mailtrap

Note: The package version used must be 2.0.0. or higher. 

Upon installation, create a mail object. The code for that should look like this, but with proper details for things such as sender, receiver, subject, and text, of course.

Also, do keep in mind that the sender needs to be an email address under the domain you added and verified earlier. 

import mailtrap as mt

# create mail object
mail = mt.Mail(
    sender=mt.Address(email="mailtrap@example.com", name="Mailtrap Test"),
    to=[mt.Address(email="your@email.com")],
    subject="You are awesome!",
    text="Congrats for sending test email with Mailtrap!",
)

The next and last step entails creating a client and sending off the email, which is done with this short snippet: 

client = mt.MailtrapClient(token="your-api-key")
client.send(mail)

As you can see, the snippet does require inserting your Mailtrap API key. This can be found in your Mailtrap account, under Settings-> API Tokens.

And there you have it, all the steps needed to send a simple email with Django covered!

For sending a bit more complex emails, with recipients in CC and BCC, attachments, an HTML body, and more, please refer to the code below and the official Mailtrap Python client page.

import base64
from pathlib import Path

import mailtrap as mt

welcome_image = Path(__file__).parent.joinpath("welcome.png").read_bytes()

mail = mt.Mail(
    sender=mt.Address(email="mailtrap@example.com", name="Mailtrap Test"),
    to=[mt.Address(email="your@email.com", name="Your name")],
    cc=[mt.Address(email="cc@email.com", name="Copy to")],
    bcc=[mt.Address(email="bcc@email.com", name="Hidden Recipient")],
    subject="You are awesome!",
    text="Congrats for sending test email with Mailtrap!",
    html="""
    <!doctype html>
    <html>
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      </head>
      <body style="font-family: sans-serif;">
        <div style="display: block; margin: auto; max-width: 600px;" class="main">
          <h1 style="font-size: 18px; font-weight: bold; margin-top: 20px">
            Congrats for sending test email with Mailtrap!
          </h1>
          <p>Inspect it using the tabs you see above and learn how this email can be improved.</p>
          <img alt="Inspect with Tabs" src="cid:welcome.png" style="width: 100%;">
          <p>Now send your email using our fake SMTP server and integration of your choice!</p>
          <p>Good luck! Hope it works.</p>
        </div>
        <!-- Example of invalid for email html/css, will be detected by Mailtrap: -->
        <style>
          .main { background-color: white; }
          a:hover { border-left-width: 1em; min-height: 2em; }
        </style>
      </body>
    </html>
    """,
    category="Test",
    attachments=[
        mt.Attachment(
            content=base64.b64encode(welcome_image),
            filename="welcome.png",
            disposition=mt.Disposition.INLINE,
            mimetype="image/png",
            content_id="welcome.png",
        )
    ],
    headers={"X-MT-Header": "Custom header"},
    custom_variables={"year": 2023},
)

client = mt.MailtrapClient(token="your-api-key")
client.send(mail)

Testing email sending in Django 

Once you’ve got everything prepared for sending email messages, it is necessary to do some initial testing of your mail server. In Python, this can be done with one command:

python -m smtpd -n -c DebuggingServer localhost:1025

This allows you to send emails to your local SMTP server. The DebuggingServer feature won’t actually send the email but will let you see the content of your message in the shell window. That’s an option you can use off-hand.

And don’t forget to update your configuration to send emails to a local server.

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'localhost'
EMAIL_HOST_USER = False
EMAIL_HOST_PASSWORD = False
EMAIL_PORT = '1025'
EMAIL_USE_TLS = False
EMAIL_USE_SSL = False

Django’s TestCase

TestCase is a solution to test a few aspects of your email delivery. It uses locmem.EmailBackend, which, as you remember, stores messages in the local memory cache – django.core.mail.outbox. So, this test runner does not actually send emails. Once you’ve selected this email backend

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

you can use the following unit test sample to test your email sending capability.

from django.core import mail
from django.test import TestCase

class EmailTestCase(TestCase):
    def test_send_email(self):
        mail.send_mail(
            'That’s your subject', 'That’s your message body',
            'from@yourdjangoapp.com', ['to@yourbestuser.com'],
            fail_silently=False,
        )

        self.assertEqual(len(mail.outbox), 1)        
        self.assertEqual(mail.outbox[0].subject, 'That’s your subject')
        self.assertEqual(mail.outbox[0].body, 'That’s your message body')

This code will test not only your email sending but also the correctness of the email subject and message body. 

Testing with Mailtrap

Mailtrap Email Testing is an email testing solution used to inspect and debug emails in staging, dev, and QA environments without any risk of spamming users with testing emails. It lets you validate HTML/CSS, analyze content for spam, preview emails, and forward them to inboxes. With Email Testing, you can do all the essential checks from the email testing checklist.

All you need to do is to copy the SMTP credentials from your demo inbox and tweak your settings.py. Or you can just copy/paste these four lines from the Integrations section by choosing Django in the pop-up menu. 

from unittest import TestCase

from django.core.mail import EmailMessage
from django.conf import settings
from django.template.loader import render_to_string
from django.utils.html import strip_tags


class EmailTestCase(TestCase):

    def setUp(self):
        settings.EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'    
        settings.EMAIL_HOST = 'smtp.mailtrap.io'
        settings.EMAIL_HOST_USER = '********'
        settings.EMAIL_HOST_PASSWORD = '*******'
        settings.EMAIL_PORT = '2525'

After that, feel free to send your HTML/CSS email with an attachment to check how it goes.

subject = 'That’s your subject'
html_message = render_to_string('mail_template.html', {'context': 'values'}) plain_message = strip_tags(html_message)
from_email = 'from@yourdjangoapp.com'
to = 'to@yourbestuser.com'

message = EmailMessage(subject=subject, body=plain_message, from_email=from_email, to=(to,))
with open('Attachment.pdf') as file:
message.attach('Attachment.pdf', file.read(), 'file/pdf')
message.send()

Note: In this example, we are using two files, ‘mail_template.html’ in the templates folder and ‘Attachment.pdf’ in the main folder. If you want to make it work, you should create those.

If there is no message in the Mailtrap Demo inbox or there are some issues with HTML content, you need to polish your code.  

Django email libraries to simplify your life

As a conclusion to this blog post about sending emails with Django, we’ve included a brief introduction of a few libraries that will facilitate your email workflow. 

django-anymail

This is a collection of email backends and webhooks for numerous famous email services including SendGrid, Mailgun, and others. django-anymail works with the django.core.mail module and normalizes the functionality of transactional email service providers. 

django-mailer

django-mailer is a Django app you can use to queue email sending. With it, scheduling your emails is much easier. 

django-post_office

With this app, you can send and manage your emails. django-post_office offers many cool features like asynchronous email sending, built-in scheduling, multiprocessing, etc. 

django-templated-email

This app is about sending templated emails. In addition to its own functionalities, django-templated-email can be used in tow with django-anymail to integrate transactional email service providers.

How to receive emails in Django

To receive emails in Django, it is better to use the django-mailbox development library if you need to import messages from local mailboxes, POP3, IMAP, or directly receive messages from Postfix or Exim4. 

While using Django-mailbox, mailbox functions as a message queue that is being gradually processed. The library helps retrieve email messages and then erases them so they are not downloaded  again the next time.

Mailbox types supported by django-mailbox: POP3, IMAP, Gmail IMAP with Oauth2 authentication, local file-based mailboxes like Maildir, Mbox, Babyl, MH, or MMDF.

Here’s a step-by-step guide on how to quickly set up your Django-mailbox and start receiving emails.

Installation

There are two ways to install django-mailbox: 

From pip:

pip install django-mailbox

Or from the github-repository:

git clone https://github.com/coddingtonbear/django-mailbox.git
cd django-mailbox
python setup.py install
  • After installing the package, go to settings.py file of the django project and add django_mailbox to INSTALLED_APPS.
  • Then, run python manage.py migrate django_mailbox from your project file to create the necessary database tables.
  • Finally, go to your project’s Django Admin and create a mailbox to consume.
  • Don’t forget to verify if your mailbox was set up right. You can do that from a shell opened to your project’s directory, using the getmail command running python manage.py getmail

When you are done with the installation and checking the configurations, it’s time to receive incoming emails. There are five different ways to do that.

  1. In your code

Use the get_new_mail method to collect new messages from the server.

  1. With Django Admin

Go to Django Admin, then to ‘Mailboxes’ page, check all the mailboxes you need to receive emails from. At the top of the list with mailboxes, choose the action selector ‘Get new mail’ and click ‘Go’.

  1. With cron job

Run the management command getmail in python manage.py getmail

  1. Directly from Exim4

To configure Exim4 to receive incoming mail begin with adding a new router:

django_mailbox:
  debug_print = 'R: django_mailbox for $localpart@$domain'
  driver = accept
  transport = send_to_django_mailbox
  domains = mydomain.com
  local_parts = emailusernameone : emailusernametwo

In case the email addresses you are trying to add are handled by other routers, disable them. For this change, the contents of local_parts must match a colon-delimited list of usernames for which you would like to receive mail.

5. Directly from Postfix

With Postfix get new mail to a script using pipe. The steps to set up receiving incoming mail directly from Postfix are pretty much the same as with Exim4. However, you might need to check out the Postfix pipe documentation

There’s also an option to subscribe to the incoming django-mailbox signal if you need to process your incoming mail at the time that suits you best.

Use this piece of code to do that:

from django_mailbox.signals import message_received
from django.dispatch import receiver

@receiver(message_received)
def dance_jig(sender, message, **args):
    print "I just received a message titled %s from a mailbox named %s" % (message.subject, message.mailbox.name, )

Keep in mind that this should be loaded to models.py or elsewhere early enough for the signal not to be fired before your signal handler’s registration is processed.

We hope that you find our guide helpful and the list of packages covered help facilitate your email workflow. You can always find more apps at Django Packages

Article by Dmitriy Shcherbakan Full Stack Developer @Railsware

Comments

3 replies

will

this does not explain how to recieve emails, blatant lie on the title. shameful

mama

yes!

Hattie

Very nice рost. I just stumbⅼed upon your blog and wisheԀ to sаy that I have truly
enjoyed surfing arоund yߋur bloց pоsts. After all I’ll be subscribing to your rss feeⅾ and I hope you write again very soon!

Comments are closed.