Django email service
Email has become a staple of practically all applications that live on the web. Whether an application requires sending email for sign-up purposes, notifications or confirming a purchase, it's hard to imagine a web application that doesn't require some kind of email functionality.
For Django projects, there are two main aspects associated with setting up email. The first step is setting up the connection to an email server and the second is the composition of emails.
Set up a default connection to an email server
Django supports connections to
any email server and also offers various options to simulate email
server connections. Email simulation is particularly powerful
during development and testing where sending out real emails is
unnecessary. The set up for an email server in Django is done in
settings.py
. Depending on the email server connection,
you may need to set up several variables in
settings.py
. Table 5-3 illustrates various email
server options for Django.
Table 5-3. Django email server configurations
Django email backend | Configuration | Description / Notes |
---|---|---|
For development (DEBUG=True) | ||
Console Email | EMAIL_BACKEND='django.core.mail.backends.console.EmailBackend' | Sends all email output to the console where Django is running. |
File Email |
EMAIL_BACKEND='django.core.mail.backends.filebased.EmailBackend' EMAIL_FILE_PATH='/tmp/django-email-dev' |
Sends all email output to a flat file specified in EMAIL_FILE_PATH. |
In memory Email | EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend' | Sends all email output to an in memory attribute available at django.core.mail.outbox. |
Nullify Email | EMAIL_BACKEND='django.core.mail.backends.dummy.EmailBackend' | Does nothing with all email output. |
Python Email Server Simulator |
EMAIL_BACKEND='django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST=127.0.0.1 EMAIL_PORT=2525 Also needed is the Python command line email server: python -m smtpd -n -c DebuggingServer localhost:2525 |
Sends all email output to a Python email server set up via command line. This is similar to the Console Email option, because the Python email server outputs content to the console. |
For production (DEBUG=False) | ||
SMTP Email Server (Standard) |
EMAIL_BACKEND='django.core.mail.backends.smtp.EmailBackend' 1EMAIL_HOST=127.0.0.1 1EMAIL_PORT=25 2EMAIL_HOST_USER=<smtp_user> 2EMAIL_HOST_PASSWORD=<smtp_user_pwd> |
Sends all email output to a SMTP email server. |
SMTP Email Server (*Secure-TLS) |
EMAIL_BACKEND='django.core.mail.backends.smtp.EmailBackend' 1EMAIL_HOST=127.0.0.1 1EMAIL_PORT=587 2EMAIL_HOST_USER=<smtp_user> 2EMAIL_HOST_PASSWORD=<smtp_user_pwd> EMAIL_USE_TLS=True |
Sends all email output to a secure SMTP (TLS) email server. |
SMTP Email Server (*Secure-SSL) |
EMAIL_BACKEND='django.core.mail.backends.smtp.EmailBackend' 1EMAIL_HOST=127.0.0.1 1EMAIL_PORT=465 2EMAIL_HOST_USER=<smtp_user> 2EMAIL_HOST_PASSWORD=<smtp_user_pwd> EMAIL_USE_SSL=True |
Sends all email output to a secure SMTP (SSL) email server. |
1 If the SMTP email server is running on a network or a different port than the default, adjust EMAIL_HOST and EMAIL_PORT accordingly. | ||
2 In today's email spam infested Internet, nearly all SMTP email servers require authentication to send email. If your SMTP server doesn't require authentication you can omit EMAIL_HOST_USER and EMAIL_HOST_PASSWORD. | ||
* The terms SSL and TLS are often used interchangeably or in conjunction with each other (TLS/SSL). There are differences though in terms of their underlying protocol. From a Django set up prescriptive, you only need to ensure what type of secure email server you connect to, as they operate differently and on different ports. |
Whichever email connection you
set up from table 5-3 in settings.py
is considered a
Django project's default and is used when doing any email related
task -- unless you specify otherwise when doing an email task.
Set up a default connection to third party email providers
The previous section provided the most generic approach to set up a default connection to an email server in Django. However, with the complexities involved in running email servers in today's world -- namely spam filtering and security issues -- it can be easier and more practical to use a third party service to relay email from a Django project to the outside world.
Although you can use the previous section's configurations to connect to any third party email service, there can be certain subtleties to set up configurations to third party email services. In this section I'll provide the Django configuration details for what I consider three of the most popular third party email services.
Although you can set up Django to deliver email to a local email application (i.e. running on 127.0.0.1) such as Exim, Postfix or Sendmail, which then deliver email to third party providers. I personally would not recommend this alternative as it adds another component to set up, maintain and worry about. Not to mention this is beyond the scope of Django, as it involves setting up different email apps with third party email services.
This following sections describe how to set up Django to connect directly with third party email providers.
Email with Google Gmail/Google Apps
Google offers the ability to send
out email through Gmail or Google Apps, the last of which is a
Gmail version for custom domains (e.g. coffeehouse.com ). Once you
have a Gmail or Google Apps account, you'll need to set up the
account's username/password credentials in
settings.py
.
You will not be able to use
Google's email services without hard-coding your account
credentials somewhere in your app. If you are weary of hard-coding
the username/password credentials in settings.py
, I
suggest you create a separate account for this purpose to limit
vulnerabilities, look into using multiple environments or
configuration files for Django to keep the username/password in a
different file or set up a local email server with the credentials
as described in the previous sidebar.
Listing 5-21 illustrates the configuration needed to set up Django to send email via Gmail or Google Apps account.
Listing 5-21 Django email configuration for Gmail or Google Apps account
EMAIL_BACKEND='django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST='smtp.gmail.com' EMAIL_PORT=587 EMAIL_HOST_USER='username@gmail.com/OR/username@coffeehouse.com' EMAIL_HOST_PASSWORD='password' EMAIL_USE_TLS=True
As you can see in listing 5-21, the configuration parameters are pretty similar to those described in table 5-3. This is all you need to set up a default email connection to Gmail or Google Apps in Django.
Caution Beware of sending too much email with Google. Since Google's email service is free, it's not designed for relaying too many email messages. If your Django app sends out a couple of email messages every hour you probably won't have a problem, but if your app sends out email messages every second or hundreds of email messages in the span of a few minutes, the account is likely to be blocked. If the account is blocked, you will either need to wait a few hours or manually log into the account (i.e. via a browser) for it to be unblocked. If the account is constantly blocked due to the email volume you send out, you should try another email service provider.
Note Google overwrites the From: email field with the Google account value, unless it's added as an alias. Django allows you to set an email's From: field to any value you want and defaults to the EMAIL_HOST_USER value in settings.py. However, to avoid spoofing, Google overwrites this field to the Google account email if the From: email value is not an alias in the Gmail or Google App account. This means if you send an email message in Django with From: support@coffeehouse.com and this email is not set up as an alias in the Gmail or Google App account, the final email appears with From: set to the Google account's main email.
Email with Amazon Simple Email Service (SES)
SES is another email service offered by AWS which is run by Amazon.com. Unlike Google's email service, SES is a paid service with an average cost of 0.0001 cents per email (10 cents per 1000 emails). The easiest way to set up Django with SES is through the Python library boto and a custom Django email backend called django-ses.
Listing 5-22 illustrates the pip requirements to install boto which is a library to integrate multiple AWS services using Python and django-ses which is an open-source project specifically designed to run SES with Django.
Listing 5-22. Python pip requirements for Amazon.com SES with Django
pip install boto pip install django-ses
Once you install the Python
packages in listing 5-22 using pip, you can proceed to configure
SES in settings.py
. Listing 5-23 illustrates the
necessary variables to set up Django to use SES.
Listing 5-23. Django email configuration for Amazon.com SES
EMAIL_BACKEND = 'django_ses.SESBackend' AWS_ACCESS_KEY_ID = 'FZINISSZ3542DPIO32CQ' AWS_SECRET_ACCESS_KEY = '3Nto4vknl+xeZR+1tF3L645EUyOS+zZy/uPJ1rN'
As you can see in listing 5-23,
the variable EMAIL_BACKEND
is set to the custom class
django_ses.SESBackend
which provides all the necessary
hooks to connect to SES.
To connect to SES you'll also
need to provide the variables AWS_ACCESS_KEY_ID
and
AWS_SECRET_ACCESS_KEY
which are access credentials
related to your AWS account. These last values are provided in your
AWS account[8].
This is all you need to set up a
default email connection to Amazon Simple Email Service (SES).
There's no need to set up any other variable in
settings.py
, such a EMAIL_HOST
or
EMAIL_HOST_USER
, everything is taken care of by the
custom email backend.
Email with SparkPost
SparkPost is another third party email service used by large companies like Twitter, Oracle and PayPal. Pricing wise SparkPost is a mix between the two previous services, it's a free service for the first 100,000 emails per month, but after this volume it's a paid service with an average cost of .0002 cents per email (20 cents per next 1000 emails per month) and lower per email rates once you send 1 million emails a month.
The easiest way to set up Django
with SparkPost is directly in settings.py.
Listing
5-24 illustrates the necessary variables to set up Django to use
SparkPost.
Listing 5-24 Django email configuration for SparkPost
EMAIL_BACKEND='django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = 'smtp.sparkpostmail.com' EMAIL_PORT = 587 EMAIL_HOST_USER = 'SMTP_Injection' EMAIL_HOST_PASSWORD = '<sparkpost_api_key>' EMAIL_USE_TLS = True
As you can see in listing 5-24,
the configuration parameters are pretty similar to those described
in table 5-3 for a standard email connection. Just notice that in
addition to the EMAIL_HOST_USER
value being
SMTP_Injection
-- a SparkPost requirement -- you'll
also need to assign a SparkPost API key to the
EMAIL_HOST_PASSWORD
. The SpakPost API key is created
in your SparkPost account[9].
Now that you understand the various ways to set up an email connection in a Django project, lets explore the actual composition of emails.
Built-in helpers to send email
There can be many options and steps involved in sending an email. To simplify this process, Django offers four shortcut methods you can leverage anywhere in an application (e.g. when a sign-up is made, when a purchase is made, when a critical error occurs). Table 5-4 illustrates the various email shortcut methods.
Table 5-4. Django email shortcut methods
Shortcut method and description | Shortcut method with all arguments* | Argument descriptions and notes |
---|---|---|
send_mail is the most common option to send
email. |
send_mail(subject, message,
from_email=settings.DEFAULT_FROM_EMAIL, recipient_list,
fail_silently=False, auth_user=None, auth_password=None,
connection=None, html_message=None) |
|
send_mass_mail is more efficient than the send_mail
method. This is the preferred choice when sending multiple emails
because it opens a single connection to the email server and sends
all messages contained in a tuple. Note however send_mass_mail does
not support HTML messages like send_mail. |
send_mass_mail(datatuple, fail_silently=False,
auth_user=None, auth_password=None, connection=None) |
|
mail_admins sends email to all users defined in the
ADMINS variable in settings.py. |
mail_admins(subject, message, fail_silently=False,
connection=None, html_message=None) |
Email is sent with a From: field set to the variable SERVER_EMAIL in settings.py which by default is root@localhost. The email subject is prefixed with the variable EMAIL_SUBJECT_PREFIX in settings.py which by default is '[Django] '. |
mail_managers sends email to all users defined in the
MANAGERS variable in settings.py. |
mail_managers(subject, message, fail_silently=False,
connection=None, html_message=None) |
Email is sent with a From: field set to the variable SERVER_EMAIL in settings.py which by default is root@localhost. The email subject is prefixed with the variable EMAIL_SUBJECT_PREFIX in settings.py which by default is '[Django] '. |
* Method arguments without a default value (e.g. subject,message) must always be provided. Method arguments with a default value (e.g. fail_silently=False, connection=None) are optional. |
Note If you start to get emails with error
messages once a project goes into production (i.e.DEBUG=False),
it's because the mail_admins
shortcut is automatically hooked-up
for this purpose. This is due to the way Django's default logging
works. To disable this behavior you will either need to clear all
values from ADMINS in settings.py or override the default logging
behavior as described in the previous section on
logging.
Custom email: Attachments, headers, CC, BCC and more with EmailMessage.
Although the previous email shortcut methods can be used under most circumstances, they do not support things like attachments, CC, BCC or other email headers. If you want total control for sending email messages in Django the previous shortcut methods in table 5-4 won't work.
Used 'under-the-hood' by the
previous Django shortcut methods and offering the utmost
flexibility for sending email in Django is the Django
EmailMessage
class. The various parameters and methods
supported by the EmailMessage
class are described in
table 5-5.
Table 5-5. Django EmailMessage class parameters and methods
Parameter and/or method | Description |
---|---|
subject | The subject line of the email. |
body | The body text as a plain text message. |
from_email | The sender's address. Both plain email (e.g.
webmaster@coffeehouse.com ) and full name with email
(e.g. Webmaster <webmaster@coffeehouse.com> )
format are acceptable. If omitted, the DEFAULT_FROM_EMAIL value
from settings.py is used. |
to | A list or tuple of recipient addresses. |
cc | A list or tuple of recipient addresses used in the the email CC header when sending the email. |
bcc | A list or tuple of addresses used as the email BCC header when sending the email. |
connection | An email backend instance. Use this parameter if you want to use the same connection for multiple messages. If omitted, a new connection is created when send() is called. |
attachments | A list of attachments to put on the message. These can be either email.MIMEBase.MIMEBase instances, or (filename, content, mimetype) triples. |
headers | A dictionary of extra headers to put on the message. The keys are the header name, values are the header values. It's up to the caller to ensure header names and values are in the correct format for an email message. The corresponding attribute is extra_headers. |
send(fail_silently=False) | Sends the message. If a connection was specified when the email was constructed, that connection is used. Otherwise, an instance of the default backend is instantiated and used. If the keyword argument fail_silently is True, exceptions raised while sending the message are omitted. An empty list of recipients does not raise an exception. |
message() | Useful when extending the EmailMessage class to override and put the content you want into the MIME object. Constructs a django.core.mail.SafeMIMEText object (a subclass of Python's email.MIMEText.MIMEText class) or a django.core.mail.SafeMIMEMultipart object holding the message to be sent. |
recipients() | Useful when extending the EmailMessage class because the SMTP server needs to be told the full list of recipients when the message is sent. It returns a list of all the recipients of the message, whether they're recorded in the to, cc or bcc attributes. |
attach() | Creates a file attachment and adds it to the message. There are two ways to call attach(). You can pass it a single argument that is an email.MIMEBase.MIMEBase instance that gets inserted directly into the resulting message or you can passs it three arguments: filename, content and mimetype (e.g.message.attach('menu.png', img_data, 'image/png'), where filename is the name of the file attachment as it will appear in the email, content is the data that will be contained inside the attachment and mimetype is the optional MIME type for the attachment. If you omit mimetype, the MIME content type is guessed from the filename of the attachment. |
attach_file() | Creates an attachment using a file from the filesystem. It can be called with the path of the file to attach (e.g.message.attach_file('/images/menu.png') and optionally with the MIME type to use for the attachment (e.g.message.attach_file('/images/menu.png','image/png'). If the MIME type is omitted, it's guessed from the filename. |
With a clear idea of the
functionalities provided by the EmailMessage
classs in
table 5-5, lets take a look at some typical cases where you would
use the EmailMessage class to send email.
Listing 5-25 provides a basic email example that uses options like CC, BCC and the Reply-To header which aren't support via the Django email shortcuts from the last section table 5-4.
Listing 5-25 Send basic email with EmailMessage class
from django.core.mail.message import EmailMessage # Build message email = EmailMessage(subject='Coffeehouse specials', body='We would like to let you know about this week\'s specials....', from_email='stores@coffeehouse.com', to=['ilovecoffee@hotmail.com', 'officemgr@startups.com'], bcc=['marketing@coffeehouse.com'], cc=['ceo@coffeehouse.com'] headers = {'Reply-To': 'support@coffeehouse.com'}) # Send message with built-in send() method email.send()
As you can see in listing 5-25,
the EmailMessage
instance is created specifying its various class
parameters. Once this is done, you just call the
send()
method to send the email. It's as simple as
that. Because no connection values are provided in the
EmailMessage
instance in listing 5-25, Django uses the
default backend connection defined in settings.py
which can be any option in table 5-3.
One drawback of the
EmailMessage
send()
method is that it
opens a connection to the email server every time it's called. This
can be inefficient if you send hundreds or thousands of emails at
once. In the spirit of the send_mass_mail()
shortcut
method from the last section in table 5-4, it's also possible to open a single
connection to the email server and send multiple emails with
EmailMessage
. Listing 5-26 shows how to use a single
connection and send multiple emails with
EmailMessage
.
Listing 5-26 Send multiple emails in a single connection with EmailMessage class
from django.core import mail # Get connection connection = mail.get_connection() # Manually open the connection connection.open() # Build message email = EmailMessage(subject='Coffeehouse specials', body='We would like to let you know about this week\'s specials....', from_email='stores@coffeehouse.com', to=['ilovecoffee@hotmail.com', 'officemgr@startups.com'], bcc=['marketing@coffeehouse.com'], cc=['ceo@coffeehouse.com'] headers = {'Reply-To': 'support@coffeehouse.com'}) # Build message email2 = EmailMessage(subject='Coffeehouse coupons', body='New coupons for our best customers....', from_email='stores@coffeehouse.com', to=['officemgr@startups.com','food@momandpopshop.com'], bcc=['marketing@coffeehouse.com'], cc=['ceo@coffeehouse.com'] headers = {'Reply-To': 'support@coffeehouse.com'}) # Send the two emails in a single call connection.send_messages([email, email2]) # The connection was already open so send_messages() doesn't close it. # We need to manually close the connection. connection.close()
In listing 5-26 the first step is
to create a connection to the email server using
mail.get_connection()
and then open the connection
with the open()
method. Next, you create the various
EmailMessage
instances. Once the email instances are
prepared, you call the connection's send_messages()
method with an argument list corresponding to each of the
EmailMessage
instances. Finally, once the emails are
sent you call the connection's close()
method to drop
the connection to the email server.
Another common email scenario is
to send HTML emails. Django provides the
EmailMultiAlternatives
class for this purpose which is
a subclass of the EmailMessage
class. By being a
subclass, it means you can leverage the same functionalities as
EmailMessage
(e.g. CC, BCC), but you don't need to do
a lot of work as the subclass EmailMultiAlternatives
is specifically designed to handle a multiple types of messages.
Listing 5-27 illustrates how to use the
EmailMultiAlternatives
class.
Listing 5-27 Send HTML (w/text) emails with EmailMultiAlternatives, a subclass of the EmailMessage class.
from django.core.mail import EmailMultiAlternatives subject, from_email, to = 'Important support message', 'support@coffeehouse.com', 'ceo@coffeehouse.com' text_content = 'This is an important message.' html_content = ' This is an important message. ' msg = EmailMultiAlternatives(subject=subject, body=text_content, from_email=from_email, to=[to]) msg.attach_alternative(html_content, "text/html") msg.send()
Listing 5-27 first define all the
email fields, which include the text and HTML version of the email.
Note that having a text and HTML version of the email content is
common practice, since there's no guarantee end users will allow or
can read HTML email, so a text version is provided as a backup.
Next, you define an instance of the
EmailMultiAlternatives
class, notice the parameters
are inline with those of the EmailMessage
class.
Next, in listing 5-27 you can see
a call to the attach_alternative
method which is
specific to the EmailMultiAlternatives
class. The
first argument to this method is the HTML content and the second is
the content type that corresponds to text/html
.
Finally, listing 5-27 calls the send()
method -- part
of the EmailMessage
class, but which is also
automatically part of to EmailMultiAlternatives
since
it's a subclass -- to send the actual email.
In controlled environments (e.g.
corporate email) where it can be guaranteed that all end users are
capable of viewing HTML email, it can be practical to just send an
HTML version of an email and bypass the text version altogether.
Under these circumstances, you can actually use the
EmailMesssage
class directly with a minor tweak.
Listing 5-28 illustrates how to send just HTML email with the
EmailMessage
class.
Listing 5-28 Send HTML emails with EmailMessage class
subject, from_email, to = 'Important support message', 'support@coffeehouse.com', 'ceo@coffeehouse.com' html_content = ' This is an important message. ' msg = EmailMessage(subject=subject, body=html_content, from_email=from_email, to=[to]) msg.content_subtype = "html" # Main content is now text/html msg.send()
Listing 5-28 looks like a
standard EmailMessage
process definition, however,
line four -- msg.content_subtype
-- is what makes
listing 5-28 different. If the HTML content were sent without line
setting msg.content_subtype
, end users would receive a
verbatim version of the HTML content (i.e. without the HTML tags
rendered). This is because by default the EmailMessage
class specifies the content type as text. In order to switch the
default content type of an EmailMessage
instance, in
line four a call is made to set the content_subtype
to
html
. With this change the email content type is set
to HTML and end users are capable of viewing the content rendered
as HTML.
Although sending an HTML email version is quicker than sending a text and HTML email version, this can be problematic if you can't determine where end users read their email. There are certain users that for security reasons disable the ability to view HTML emails, as well as certain email products that can't or aren't very good at rendering HTML emails. So if you just send an HTML email version, there can be a subset of end users that won't be able to see the email content.
For this reason if you send email to end users where you can't control their environment (i.e. email reader), it is best you send a text and HTML email version -- as illustrated in listing 5-27 -- than sending an HTML email version illustrated in listing 5-28.
Another common practice when sending emails is to attach files. Listing 5-29 illustrates how to attach a PDF to an email.
Listing 5-29 Send email with PDF attachment with EmailMessage class
from django.core.mail.message import EmailMessage # Build message email = EmailMessage(subject='Coffeehouse sales report', body='Attached is sales report....', from_email='stores@coffeehouse.com', to=['ceo@coffeehouse.com', 'marketing@coffeehouse.com'] headers = {'Reply-To': 'sales@coffeehouse.com'}) # Open PDF file attachment = open('SalesReport.pdf', 'rb') # Attach PDF file email.attach('SalesReport.pdf',attachment.read(),'application/pdf') # Send message with built-in send() method email.send()
As you can see in listing 5-29,
after creating an EmailMessage
instance you just open
the PDF file using Python's standard open()
method.
Next, you use the attach()
method from the
EmailMessage
which takes three arguments: the file
name, the file contents and the file content type or MIME type.
Finally, a call is made to the send()
method to send
the email.