How to Send an Email with boto and SES

Introduction

Pretty much every user-based app and website needs to send an email to the user at some point, so eventually you'll have to deal with the joyous world of programmatic emailing. There are quite a few services popping up to help you with this, but with every app having its own unique requirements, few of these services adequately get the job done.

So having faced the problem a few times (and in a few languages), I decided to write up this short post on how to send email with boto and SES. The boto library is a very nice wrapper for Python that helps you interact with the AWS API. To see some more of it's capabilities, check out my other boto article on how to upload a file to s3.

Setup

Before you can really do anything, you'll need an AWS account (obviously) along with the access key and secret key to your account, which will be used for authenticating with the SES servers. There are a few different ways you can use the keys to authenticate, but here we'll just be passing them to the connect_to_region method provided by boto.

Next, you'll need to either verify with SES an email address (a GMail address works just fine) or a domain you own. If you're just testing this feature out, I'd suggest just verifying a single email address since it's a bit quicker. All you need to do is click on a link in the verification email they send you, as opposed to adding TXT records to your zone file for verifying a domain.

If you're just using SES for the first time and your application will need to send out a high volume of emails, then you may need to submit a request to increase your Sending Quota. Your SES account is initially put in to a 'sandbox', which means you can only send 200 emails within a 24 hour period. Once you've successfully requested to increase your sending limits then you should be able to send many more (I was approved for 50,000 per day, which is far more than I need).

The Code

Once you've done the initial setup I mentioned above, you should be able to use the code below to send emails.

import boto.ses

AWS_ACCESS_KEY = 'YOUR-ACCESS-KEY-HERE'  
AWS_SECRET_KEY = 'YOUR-SECRET-KEY-HERE'

class Email(object):  
    def __init__(self, to, subject):
        self.to = to
        self.subject = subject
        self._html = None
        self._text = None
        self._format = 'html'

    def html(self, html):
        self._html = html

    def text(self, text):
        self._text = text

    def send(self, from_addr=None):
        body = self._html

        if isinstance(self.to, basestring):
            self.to = [self.to]
        if not from_addr:
            from_addr = 'me@example.com'
        if not self._html and not self._text:
            raise Exception('You must provide a text or html body.')
        if not self._html:
            self._format = 'text'
            body = self._text

        connection = boto.ses.connect_to_region(
            'us-east-1',
            aws_access_key_id=AWS_ACCESS_KEY, 
            aws_secret_access_key=AWS_SECRET_KEY
        )

        return connection.send_email(
            from_addr,
            self.subject,
            None,
            self.to,
            format=self._format,
            text_body=self._text,
            html_body=self._html
        )

To use this code, all you need to do is this:

email = Email(to='s.w.robinson@gmail.com', subject='OMG You are HTML Awesome')  
email.text('This is a text body. Foo bar.')  
email.html('<html><body>This is a text body. <strong>Foo bar.</strong></body></html>')  # Optional  
email.send()  

The email.html() call is optional. If you include both text and HTML in your email then both will be included in the resulting MIME and the email client will show whichever format is supported or preferred by the user.

Using Email Templates

If you want to get really fancy, try using a template engine. Instead of passing the email body string directly, we can load it from a template, much like you would render an HTML page in a web framework like Django.

Here we use the Jinja2 templating engine to handle the template loading and rendering:

import boto.ses  
from jinja2 import Environment, PackageLoader

# Loads templates from the yourapp.templates folder
env = Environment(loader=PackageLoader('yourapp', 'templates'))

AWS_ACCESS_KEY = 'YOUR-ACCESS-KEY-HERE'  
AWS_SECRET_KEY = 'YOUR-SECRET-KEY-HERE'

class Email(object):  
    def __init__(self, to, subject):
        self.to = to
        self.subject = subject
        self._html = None
        self._text = None

    def _render(self, filename, context):
        template = env.get_template(filename)
        return template.render(context)

    def html(self, filename, context):
        self._html = self._render(filename, context)

    def text(self, filename, context):
        self._text = self._render(filename, context)

    def send(self, from_addr=None):
        # Same as before...

Using this code is similar to before, but instead we'll just pass the template filename and the context from which the template will be populated:

email = Email(to='you@example.com', subject='OMG an Email!')  
ctx = {'username': user.username}  
email.text('email.txt', ctx)  
email.html('email.html', ctx)  # Optional  
email.send()  

Now you can easily create and render HTML emails just like you do webpages.

Conclusion

Hopefully this short tutorial was useful to you. The code here should be good for most use-cases, although you can get even more advanced by adding Cc, Bcc, reply-to addresses, return paths, or even file attachments.

All of these extra features I just mentioned, except attachments, can be handled by the send_email function. To send attachments you'll have to use the lower-level send_raw_email function, which requires you to construct the MIME message yourself. Don't worry, that isn't too hard thanks to Python's email package, but we'll save that for another article.

Was this useful to you? Did you find a mistake? Let me know in the comments!