FeelGood, an Example Project for AWS
The hackathon organizers wanted to build an application that facilitates self-service for good vibes and positivity.
From that ideal, the project grew to provide additional registration options to facilitate laughs and silliness.
Read about the project and how we built it!
- Text Messages: uplifting content, delivered straight to your phone.
- Mix and Match Subscriptions: users should be able to register for message streams that fit their personality.
- Fully Cloud Hosted: The end-to-end deployment should be fully hosted in the cloud, without running a VM or using local computing resources.
automatic, supersonic, hypnotic, funky fresh
- A user goes to the application’s website to sign up for messages.
- The user subscribes to a topic.
- The user gets a welcome message after subscribing to a topic of their choice. They can continue to subscribe to other topics they are interested in.
- At set times during the day, the user gets a text message reminding them to:
- Feel good
- Drink water
- Commune with their crow friends
Of course, the messages they actually get are dependent on which topics they subscribed to.
To realize these desired behaviors & user experiences, we used AWS services and deployed the application to the cloud. The application’s design is described below.
- An S3 bucket is used to host the subscription site. We configured domain redirection for a custom domain and SSL certificates for HTTP/S traffic.
- The user interacts with the site as they would any other.
- Upon clicking the
- The Lambda function takes the JSON payload delivered by the POST request, which contains the user’s phone number and selected topic. The function then:
- SNS saves the user’s phone number as a subscriber under the specified topic.
- If the Lambda function processes successfully, it will return a success code to the API Gateway method, which in turn returns the HTTP 200 status to the user’s browser. The Lambda function also publishes a text message to the user’s provided number, which confirms subscription and welcomes the user.
Here’s a diagram of the system’s flow of logic:
- To send messages out at regular intervals, we used CloudWatch Events to create rules which get invoked on a time schedule.
- When creating the Event rule, the message publication function was selected as the target.
- Upon being invoked, the Lambda function:
- Chooses 4 messages to publish (one for each possible topic)
- Looks up the ARN for each topic
- Invokes the Simple Notification Service SDK
- Makes 4
publish()calls to SNS, where each call uses a different message and topic ARN
- SNS receives the SDK invocation to publish a message and distributes the message for each topic to all subscribers for that topic. Each topic is handled asynchronously.
Here’s a diagram of the subsystem’s flow of logic:
The specific instructions, how-tos, and details we encountered are organized here by AWS product.
S3 & Website Construction
We used to the following resources as a guide for configuring S3 buckets to host the subscription website:
Setting Up a Static Website Using a Custom Domain Name Registered with Route 53
This guide walks through how to register a custom domain using Route 53 and create/configure two S3 buckets to serve as containers for the contents of your website.
Setting up a Static Website
If you don’t wish to buy a custom domain, use this guide instead. Your website will still be publicly accessible with a link that resembles the following:
Obtaining an SSL Certificate for a Custom Domain
This guide explains how to use CloudFront to request an SSL certificate for your custom domain in order to serve content over an HTTPS protocol. Make sure to scroll down to the section titled
Using a website endpoint as the origin with anonymous (public) access allowed, which aligns with the configuration outlined in the two previous S3 setup guides.
Updating Existing Content with a CloudFront Distribution
If using CloudFront to serve your site over HTTPS, you will find updating the content of the site is not as simple as changing out the files in the S3 bucket due to CloudFront’s caching properties. This guide outlines two workarounds: using versioned file names, and invalidating the object within CloudFront which prompts it to reload objects the source.
Collecting and Submitting Form Data to Other AWS Services using HTML and jQuery AJAX requsts
Your “Contact Us” Formsection of this guide covers how to create a simple web form using HTML, and
Connecting it all Togethercovers how to use jQuery AJAX calls to send the data collected in the form to other AWS services to complete the subscription process.
There are multiple types of APIs that can be built using API Gateway, such as REST, HTTP, or WebSocket. This document will focus specifically on REST APIs. AWS’ API Gateway documentation has a helpful tutorial that covers creating a REST API with Lambda Integrations. This was the approach taken for FeelGood.
Create a Resource
A resource is something that can be accessed through the REST API using CRUD operations. GCP has a good explanation of REST APIs and resources if you are unfamiliar with the concept or would like to refresh your memory. In API Gateway, the resource also represents your endpoint. For example, on a hypothetical website for a car dealership, the URL might be
www.cars.com/buycar. The endpoint in this scenario is
/buycar. Each endpoint has its own set of methods.
Create a Method
A method is the actual operation to be performed on the resource, such as GET, PUT, POST, DELETE, etc. The method’s settings are where integrations with Lambda functions including input pass-through are set. See the API Gateway page on REST API methods for a detailed tutorial.
CORS stands for Cross-Origin Resource Sharing. This is required to tell your browser that it is safe to access certain API endpoints. Whether or not CORS is required depends on what the API does. See Mozilla’s MDN Docs on CORS for more information on what it is and why it’s needed. See API Gateway Docs on Enabling CORS for REST APIs for a tutorial on enabling CORS.
Deploy the API
Once everything is setup correctly, all that’s left is to deploy the API. You can create stages like “staging” and “production” and deploy different versions on the API to each stage. It is recommended to deploy a staging version of the API for testing before deployed the production version of the API for use in an application. Note that the name of the stage will be part of the endpoint URL. See API Gateway Docs on Deploying a REST API for a detailed walkthrough.
AWS Lambda handles all of FeelGood’s computation. Within AWS Lambda, there are several important things that must be configured in order for FeelGood’s code to operate correctly.
Give Lambda access to SNS
By default, creating a Lambda function from scratch, adding SNS code, and pressing “Test” won’t work. Why? Simple, because the Lambda function does not have access to SNS. Cloud systems operate on a least-required permissions model, meaning that in order for your Lambda function to access SNS, you must explicitly grant Lambda access to SNS (and any other AWS services you might want Lambda to access). Refer to AWS documentation on IAM permissions for SNS, and check out the TL;DR explanation of IAM on the documentation site for this hackathon.
Set up environment variables for Lambda functions
In order to keep the code looking clean, and to keep our Topic ARNs safe (more on these in the SNS section), we use environment variables to store the names of the SNS topics and their ARNs. These are exactly like environment variables on your computer. They’re variables that you can access in your code like any other, but since they’re not declared or otherwise modified in the source code, they’re invisible and can’t be accidentally leaked (say when you upload your code to a public git repository).
After you create a Lambda function in the AWS portal, you will be greeted with the function’s page. Here you can edit the code directly and modify the function’s settings. Right below the code window, you will see a menu item for Environment Variables. Click Edit to modify them. The key is the variable name, and the value is the variable’s value. Click Save to save these variables in the function. Now you can access the environment variables in the Lambda function’s code by calling it using the key. See AWS Documentation on Environment Variables for more info.
Set up aliases for Lambda functions
Lambda has a feature called Aliases. Aliases are used in combination with function versions to manage what version of the Lambda function will be called by other AWS services. In FeelGood, aliases are used to connect API Gateway endpoints to the publish and subscribe Lambda functions.
In order to create an alias, you must first have a published function version to connect it to. Under the Actions menu in the function, click on Publish new version, enter a description, and publish it to create a new version. This new version will retain the functionality that it has at the time that it is published. Refer to the AWS Docs on Lambda Versions for more detail on what you can do with versioning.
Aliases allow you to refer to different versions of a function with the same name. So for example in API Gateway you can link a GET request with a Lambda function using the alias “API_GET_Version”. Then, no matter what function version you connect to that alias, be it 1, 10, or 1000, you will not need to change the API Gateway configuration when you update the Lambda code. You do however, need to modify which function version the alias will point to each time you publish a new version. You can alias to the
$LATESTversion in order to have Lambda automatically use whatever code exists when you Save the function, but this is not recommended. You should only alias to function versions that you have tested and verified to work. Further documentation on function aliases is available here.
Configure CloudWatch Trigger Invocation
In order for FeelGood messages to be sent out at a specific time, we use AWS CloudWatch Events/EventBridge as a trigger to run the Lambda publish function at a specific time.
First, select an alias in the menu for the Lambda function that you wish to trigger. In the Designer pane, there is a button called Add trigger. Clicking on that will bring up a drop-down with a number of potential triggering services.
After clicking Add trigger, AWS will present a menu to configure a rule. This rule will trigger the Lambda function. Proceed to name the trigger.
Schedule expression is where you define the time interval that the function should be triggered at. FeelGood uses cron syntax identical to the syntax used to schedule recurring jobs in native Unix systems; a good reference on cron syntax we found useful is available here. Cron syntax uses UTC time. In FeelGood, the trigger uses the cron syntax
cron(0 18 * * ? *), which means every day at 10 AM PST/11 AM PDT. We recommend looking up a converter from UTC to your timezone. Saving and enabling this trigger will run the function at the specified time. AWS has a good explainer on creating CloudWatch Events Rules here. AWS also has a good document on how to setup CloudWatch triggers for Lambda using cron syntax.
Simple Notification Service
Create your SNS topics. This is fairly straightforward to do in the console. Give it a logical name which helps you and your collaborators understand what the topic is used for.
Manage SMS preferences for Amazon SNS. Generally speaking, if you’re sending SMS messages, you probably want to choose the
Transactionaloption instead of
Promotionalto ensure that message delivery is as reliable as possible. If you expect heavy utilization of SMS message delivery, consider requesting a budget increase for SMS delivery.
- Call the SNS SDK from your subscribe Lambda function. Our implementation uses the Python SDK. When subscribing a phone number as an SMS delivery endpoint, ensure that the subscription call specifies the
'sms', like the snippet below. The complete Lambda function is available here.
import boto3 import os client = boto3.client('sns') response = client.subscribe( # use Lambda environment variable to get ARN TopicArn=os.environ[topic], Protocol='sms', Endpoint=userNum)
- Publish messages to topics. See the AWS docs for SMS-flavored procedures for publishing messages, and check out our Lambda implementations for sending a registration confirmation message to individual numbers and publishing messages to topics.
Gotchas & Lessons Learned
- Amazon SNS may have reliability issues that are outside of your control; message delivery to subscribers is not guaranteed. If high availability or reliability is important to your application, consider incorporating an API-accessible service which specializes in SMS, such as Twilio.
- Always, always, always set up robust logging. AWS streamlines setup of logging and performance monitoring for many services via CloudWatch. This was particularly useful when troubleshooting unreliable message delivery to subscribers, monitoring S3 usage, and monitoring the frequency of API requests.