Email donors

We covered so much in this chapter and the last step is to build the emailing functionality which will notify donors from a specific city whenever there’s a new blood donation event planned in their city.

As described in the chapter title, we will be using AWS Simple Email Service to send out emails because it is easy to use from boto3 and offers 62.000 emails for free per month.

The solution roughly looks like this, and we are now at the middle step, which will batch up to 50 email addresses (a limitation of AWS SES) and send a message to them.

SES configuration

Even though using SES is relatively simple with boto3, the service itself comes with certain limitations when you first start using it. AWS calls this the “sandbox mode”.

From the docs:

In a sandbox environment, you can use all of the features offered by Amazon SES; however, certain sending limits and restrictions apply. When you’re ready to move out of the sandbox, submit a request for production access

In practice this means that:

  • we can send up to 200 emails in a 24 hour period
  • we can send up to 1 email per second
  • we can only use a “Verified identity” as the FROM email address

All of these limitations are good enough for the purposes of this workshop. I have verified an identity in advance and it will be provided to you.

Batching up to 50 emails

I was never a great developer so StackOverflow helped me on this one. Create the chalicelib/utils.py file and add in it

def chunk_list(lst, chunk_size):
    for i in range(0, len(lst), chunk_size):
        yield lst[i:i + chunk_size]

This will allow us to split a list lst with many elements into even chunks of chunk_size. This is how it works:

We can also write a test for this function. Your time to shine again.

Solution

Run pytest to make sure it is included and passing:

And finally, batching those email addresses can be done in this way:

from chalicelib.utils import chunk_list
# ... SNIP ...

@app.on_dynamodb_record(stream_arn=stream_arn)
def handle_stream(event):
    # ... SNIP ...
    city_name = stream_data.get("city").get("S")
    db_response = get_app_db().donors_by_city(city_name)
    all_emails = [elem.get("email") for elem in db_response.return_value]
    
    batched_emails = list(chunk_list(all_emails, 50))
    
    app.log.debug(f"Gathered '{len(all_emails)}' from donors")
    
    for email_batch in batched_emails:
        # send email

Emailing donors using SES

Boto3 documentation for sending an email with SES is clear and explicit (it even lists certain limitations of AWS SES sandbox mode).

It shows us how to use the SES client to send emails and that is exactly what we will do. Below are the necessary code changes:

import boto3
# ... SNIP ...
ses_client = boto3.client("ses")

@app.on_dynamodb_record(stream_arn=stream_arn)
def handle_stream(event):
    # ... SNIP ...
    batched_emails = list(chunk_list(all_emails, 50))
    
    app.log.debug(f"Gathered '{len(all_emails)}' from donors")
    
    for email_batch in batched_emails:
        ses_client.send_email(
            Source="TO_BE_PROVIDED",
            Destination={
                "ToAddresses": email_batch
            },
            Message={
                "Subject": {
                    "Data": f"New blood donation event in {city_name}",
                    "Charset": "UTF-8",
                },
                "Body": {
                    "Html": {
                        "Data": f"<p>Dear donor,</p><p>There is a new blood donation event happening in {city_name}.</p><p>Come join us!</p>",
                        "Charset": "UTF-8",
                    }
                }
            }
        )
Permissions, permissions

As always, we have to allow our function to send emails. This is done by appending the following statement to the policy-handle-stream.json file:

{
  "Action": [
    "ses:SendEmail"
  ],
  "Resource": [
      "arn:aws:ses:eu-central-1:932785857088:identity/TO_BE_PROVIDED"
  ],
  "Effect": "Allow"
}

Deploy everything and add a new donation in Haarlem

chalice deploy
# ... SNIP ...
http -b POST $(chalice url)/donation/create city=Haarlem address="Other street" datetime="2022-04-06T12:00:00"

Moment of truth

If you made it this far, congratulations 👍 It hasn’t been easy for sure.