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.

Django with EXIM, POSTFIX OR SENDMAIL

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-22 illustrates the configuration needed to set up Django to send email via Gmail or Google Apps account.

Listing 5-22 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-22, 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-23 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-23. 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-23 using pip, you can proceed to configure SES in settings.py. Listing 5-24 illustrates the necessary variables to set up Django to use SES.

Listing 5-24. 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-24, 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-25 illustrates the necessary variables to set up Django to use SparkPost.

Listing 5-25 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-25, 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)
  • subject.- Email subject string.
  • message.-Email message string.
  • from_email.- Email From: field. If not provided it's set to DEFAULT_FROM_EMAIL from settings.py which by default is webmaster@localhost.
  • recipient_list.- Email recipients as a list of strings.
  • fail_silently.- Offers the ability to bypass errors if email cannot be sent. By default set to False, which means any error when attempting to send email raises an smtplib.SMTPException exception.
  • auth_user.- Authentication user for the SMTP server. If provided it overrides the variable EMAIL_HOST_USER in settings.py.
  • auth_password.- Authentication password for the SMTP server. If provided it overrides the variable EMAIL_HOST_PASSWORD in settings.py.
  • connection.- Django email backend to send the mail. If provided it overrides the variable EMAIL_BACKEND in settings.py. See table 5-3 for options.
  • html_message.- An HTML string to send an HTML & text email message. If provided, the resulting email is a multipart/alternative email with message as the text/plain content type and html_message as the text/html content type.
  • 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)
  • datatuple.- Is a tuple which contains tuples representing an email structure in the form (subject, message, from_email=settings.DEFAULT_FROM_EMAIL, recipient_list).
  • 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-26 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-26 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-26, 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-26, 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-27 shows how to use a single connection and send multiple emails with EmailMessage.

    Listing 5-27 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-27 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-28 illustrates how to use the EmailMultiAlternatives class.

    Listing 5-28 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-28 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-28 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-28 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-29 illustrates how to send just HTML email with the EmailMessage class.

    Listing 5-29 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-29 looks like a standard EmailMessage process definition, however, line four -- msg.content_subtype -- is what makes listing 5-29 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.

    Beware of just sending HTML email versions to the public

    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-28 -- than sending an HTML email version illustrated in listing 5-29.

    Another common practice when sending emails is to attach files. Listing 5-30 illustrates how to attach a PDF to an email.

    Listing 5-30 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-30, 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.

    1. http://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys     

    2. https://support.sparkpost.com/customer/portal/articles/1933377-create-api-keys