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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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.
1 2 3 4 5 6 7 8 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | 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