Darren O'Neill

Sending triggered emails with Django using Celery and SES

This post shows how you can send triggered emails asynchronously through Amazon's SES (Simple Email Service) using Django, Celery and RabbitMQ.

First up install and run RabbitMQ:

  • Install: sudo apt-get install rabbitmq-server
  • Run: rabbitmq-server

Next install Celery following the instructions here (make sure you add the following to settings.py):

import djcelery
djcelery.setup_loader()
BROKER_URL = "amqp://guest:guest@localhost:5672//"

Finally install Django SES using this guide. Make sure you verify a sending address with Amazon and add it to your settings file:

EMAIL_FROM_ADDR = "do-not-reply@yourverifieddomain.com"

Before writing any email tasks Celery needs to be started. In development mode you can run the following: python manage.py celeryd -B -E -l info but in production you will probably want to use something like Supervisor to ensure the process is always running. You could do something like the following:

/etc/supervisor/conf.d/project_name_celery.conf:

[program:project_name_celery]
directory = /path/to/project
user = username
command = /path/to/project/config/celery.sh
stdout_logfile = /path/to/logfile
stderr_logfile = /path/to/errlog

/path/to/project/config/celery.sh

#!/bin/bash
set -e
cd /path/to/project
source /path/to/virtualenv/bin/activate
python manage.py celeryd -B -E -l info

Now everything is installed and Celery and RabbitMQ are running, tasks and triggers can be created. Creating an email task:

# app/tasks.py
from django.conf import settings
from djcelery import celery
from .services import Email

@celery.task
def send_trigger_email(*args, **kwargs):
    email = Email(to=kwargs.get('to'), subject=kwargs.get('subject'))
    context = {
        'username': kwargs.get('username', ''),
        'confirmation_id': kwargs.get('confirmation_id', '')
    }
    email.text('{0}/emails/{1}.txt'.format(
        settings.TEMPLATE_DIRS[0], kwargs.get('template')), context)
    email.html('{0}/emails/{1}.html'.format(
        settings.TEMPLATE_DIRS[0], kwargs.get('template')), context)
    email.send()

The Email class I have used can be found here. The templates are just standard Django templates, here is an example of what a text version of an email could look like:

Thanks for your order
=====================

Hey {{ username }}, thanks for your recent order. Your confirmation number
is {{ confirmation_id }}.

Thanks
Web team

How to trigger the email send:

# app/admin.py
from django.contrib import admin
from .tasks import send_trigger_email

class MyAppAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        # ...
        email_data = {
            'to': email,
            'subject': 'Thanks for your order',
            'username': username,
            'confirmation_id': confirmation_id,
            'template': 'confirm'
        }
        send_trigger_email.delay(**email_data)

        super(MyAppAdmin, self).save_model(
            request, obj, form, change
        )

Calling the task with delay ensures it will be processed asynchronously.