Serverless On-call duty notifier – Part 2

In the previous blog post, I’ve described how to build a simple SMS notification system using DynamoDB, SNS and AWS Lambda. In this post, I’ll show how to change it in order to allow each user to choose whenever he wants to get SMS notification, Email notification or nothing at all.

First of all we need to change the users table layout and add 2 additional fields to each user row. The script that is shown in the previous post for filling the database should look like this:

import boto3
 
dynamodb = boto3.resource("dynamodb")
 
if __name__ == "__main__":
    users = dynamodb.Table("oncall-notifier.names")
    with users.batch_writer() as batch:
        batch.put_item(Item={"Name": "Alexander", "Phone": "+972000000000", "Email": "alexander@company.com", "Preferred": "SMS,Email"})
        batch.put_item(Item={"Name": "Danny", "Phone": "+972000000000", "Email": "danny@company.com", "Preferred": "Email"})
        batch.put_item(Item={"Name": "Moshe", "Phone": "+972000000000", "Email": "moshe@company.com", "Preferred": "None"})
 
    dates = dynamodb.Table("oncall-notifier.dates")
    with dates.batch_writer() as batch:
        batch.put_item(Item={"Date": "2017-06-28", "Names": ["Alexander", "Danny"]})
        batch.put_item(Item={"Date": "2017-06-29", "Names": ["Alexander", "Moshe"]})
        batch.put_item(Item={"Date": "2017-06-30", "Names" : ["Danny", "Moshe"]})

As you can see, each user has 4 fields: Name, Phone, Email, Preferred. The “Preferred” field can contain the following values: “Email”, “SMS”, “None” and can combine them using comma (for example: “SMS,Email”).

For sending emails, we’ll use the AWS SES (Amazon Simple Email Service) service. In order to confirm that we really own the domain we’re sending emails to/from, we need to verify it. We simply go to the SES page, add a new domain and get some lines we need to add to our domains DNS DKIM settings. The domain verification should take 5 minutes and we’re ready to go.

Sending email with SES is done simply by using boto3 SDK for python. There are multiple options that we can use (Plain text/HTML/CC/BCC/etc.) that can be found in the boto3 documentation page.

We’ll try to keep it simple and send a simple plain text message.

ses = boto3.client("ses")
message = "On-call for today - %s" % ", ".join(people)
ses.send_email(Source="no-reply@company.com",
			   Destination={ "ToAddresses": [ item["Email"] ] },
               Message={
					"Subject": { "Data": "Company On-Call Duty" },
                    "Body": { "Text": { "Data": message }}
               })

When we read the user’s details from the database, we need to check his preferred notification way and act accordingly. The full lambda code should look like this:

from __future__ import print_function

import boto3
import datetime
import sys

def get_today_duty(dynamodb):
        today = str(datetime.datetime.now().date())
        print("Querying database for date: %s" % today)

        # Get todays people from dates table
        dates = dynamodb.Table("oncall-notifier.dates")

        response = dates.get_item(Key={ "Date" : today })
        if not("Item" in response.keys()):
                print("Failed with response:  %s" % response)
                return []

        item = response["Item"]["Names"]
        print("Found entry: %s" % item)
        return item

def get_details(dynamodb, people):
        users = dynamodb.Table("oncall-notifier.names")

        result = []
        for name in people:
                print("Querying user information for %s" % name)
                response = users.get_item(Key={ "Name" : name })
                if not ("Item" in response.keys()):
                        print("Failed with response:  %s" % response)
                        continue

                result.append(response["Item"])

        return result

def send_messages(message, people, items):
        sns = boto3.client("sns")
        ses = boto3.client("ses")

        for item in items:
                for preferred in item["Preferred"].lower().split(","):
                        try:
                            if preferred == "sms":
                                    print("Sending to %s using phone number: %s" % (item["Name"], item["Phone"]))
                                    sns.publish(Message=message, PhoneNumber=item["Phone"])
                            elif preferred == "email":
                                    print("Sending to %s using email: %s" % (item["Name"], item["Email"]))
                                    ses.send_email(Source="no-reply@company.com",
                                                    Destination={
                                                            "ToAddresses": [ item["Email"] ]
                                                    },
                                                    Message={
                                                            "Subject": { "Data": "On-Call Duty Notifier" },
                                                            "Body": { "Text": { "Data": message }}
                                                    })
                            else:
                                    print("Not sending to %s" % item["Name"])
                        except Exception as e:
                            print("Caught exception: %s" % e.message)

def lambda_handler(event, context):
        dynamodb = boto3.resource("dynamodb")
        people = get_today_duty(dynamodb)
        if len(people) == 0:
                sys.exit(1)

        details = get_details(dynamodb, people)
        if len(details) == 0:
                sys.exit(1)

        message = "On-call for today - %s" % ", ".join(people)
        send_messages(message, people, details)

Feel free to use and extend it with more interesting ways of communication.
Alexander.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s