AWS Compute Blog
Building a serverless weather bot with two-way SMS, AWS SAM, and AWS Lambda
People love being able to send text messages to companies to change appointments, get support, or receive order updates. Short message service (SMS) is ubiquitous around the world and supported in almost every mobile phone that you can find today. It can also be a good interface for a variety of business applications.
Many developers know that Amazon SNS can send one-way text messages. Fewer know about handling two-way conversations with the other services available. In the example covered in this post, you can set up and deploy two-way SMS in about 10 minutes.
This example creates a weather bot that responds to a text message from a user, providing weather information for the request weather zipcode
. This demo only works for US users, but the principles of the design apply anywhere. You receive a response back in a few seconds with a current weather report.
This post covers the following walkthroughs:
- Setting up a phone number in Amazon Pinpoint
- Deploying the serverless application using AWS SAM to respond to the text message
- Reviewing the code used in the AWS Lambda function
The final architecture looks like the following diagram:
Setting up Amazon Pinpoint
Amazon Pinpoint provides a range of different ways to send personalized messages to customers. This makes it easy to coordinate a large number of messages for web or mobile applications. It’s designed to help you drive engagement and make applications more useful to your users. This project uses the two-way text messaging feature, but Amazon Pinpoint has a broad range of other capabilities.
First, set up a phone number for this project. Amazon Pinpoint provides a dedicated number, which currently costs $1/month and is not covered by the Free Tier allowance. You are also charged for text messages, so be sure to review the current pricing before launching any application into production.
To reserve your dedicated phone number, follow these steps:
1. Sign in to the Amazon Pinpoint console.
2. Ensure that you are in a Region where Amazon Pinpoint is supported. For the most up-to-date list, see AWS Service Endpoints. This walkthrough uses us-east-1 (US East – N. Virginia).
3. On the Get started page, for Project name, enter weatherApp
, and choose Create a project.
4. On the Configure features page, for SMS and voice, choose Configure.
5. Select Enable the SMS channel for this project, and choose Save changes.
6. Choose Settings, SMS and voice.
7. Under Number settings, choose Request long codes.
For Target country or Region, choose United States. For Default call type, choose Promotional and then choose Request long codes. The confirmation page shows that a new phone number has been added to the account.
8. At the top of the page, choose All projects, and note the Project ID. You need this in the next section.
You now have a dedicated phone number ready to receive SMS messages. At the moment, the messages are not routed anywhere. You configure this in the next section.
Setting up the serverless application
Before deploying the code, you need an API key from the OpenWeatherMap service. For a free account, sign up on the Create New Account page. This service provides an API where you can post a zip code and receive the current weather conditions for that location.
Make sure that you have the AWS CLI and the AWS SAM CLI installed before proceeding. You are using these tools from the command line to automate the deployment of this application. The code for this walkthrough is stored in the aws-serverless-sar-pinpoint-weather-bot GitHub repo. You use the AWS SAM template in the repo to coordinate the deployment of the Lambda function and the SNS topic.
1. Create a new, empty directory on your local machine and clone the repository:
2. Create a bucket for the deployment (specify a unique bucket name):
Change into the cloned directory:
3. Run the AWS SAM build process and create the AWS SAM package:
4. Deploy the AWS SAM application:
- Replace
yourAPIkey
with the OpenWeatherMap API key - Replace
yourApplicationId
with the Amazon Pinpoint project ID from the first section.
At this point, you have deployed the Lambda function to process the core application logic and an SNS topic to receive the text messages. The final step is to connect the Amazon Pinpoint service with the SNS topic that has been created by this AWS SAM template.
Connect Amazon Pinpoint to Amazon SNS
Browse to the SNS console to find the topic created by the deployment, and copy the ARN to the clipboard.
To add the SNS topic to the Amazon Pinpoint project:
1. In the Amazon Pinpoint console, under All projects, select your weatherApp project.
2. In the left navigation pane, choose Settings, SMS and voice.
3. Under Number settings, choose the phone number. Expand the Two-way SMS section, and check Enable two-way SMS.
4. Under Incoming message destination, select Choose an existing SNS topic, and then select the ARN that you copied previously.
5. Choose Save.
Now you can test your deployment. Text weather zipcode
to your dedicated phone number. The service responds with the weather summary.
Reviewing the code
When Amazon Pinpoint receives the incoming text message to the dedicated phone number, it publishes the message to the SNS topic. The Lambda function subscribes to this topic and is invoked every time a new message arrives.
App.js contains the entry point for the Lambda handler, providing a top-level error handler and iterating through the event object in case multiple messages are received. Each message is sent to the smsResponder
function. This is wrapped in await Promise.all
so processing happens in parallel, because the messages are not dependent on each other.
const { smsResponder } = require('./smsResponder')
// Calls the SMS responder function for each text message passed in the event parameter.
exports.lambdaHandler = async (event, context) => {
console.log('Starting handler')
await Promise.all(
event.Records.map(async (record) => {
try {
await smsResponder(record)
} catch (err) {
console.error(err)
return err
}
})
)
return {
'statusCode': 200
}
}
smsResponder.js checks that the text message begins with the keyword (weather), followed by a valid zip code. After requesting the weather summary, it sends the response back to Amazon Pinpoint to send the SMS back to the user.
When the params
object is built to create the responding text message, this function reverses the destination and origination phone numbers from the incoming message. It marks the message as PROMOTIONAL, and sets the response channel to SMS.
const AWS = require('aws-sdk')
AWS.config.update({ region: process.env.AWS_REGION || 'us-east-1' })
const { getWeather } = require('./getWeather')
const KEYWORD = 'weather'
const validateZipCode = function (elementValue){
let zipCodePattern = /^\d{5}$|^\d{5}-\d{4}$/
return zipCodePattern.test(elementValue)
}
const sendSMS = async function (params) {
const pinpoint = new AWS.Pinpoint()
console.log('sendSMS called: ', JSON.stringify(params, null, 2))
return new Promise((resolve, reject) => {
pinpoint.sendMessages(params, function(err, data) {
if(err) {
console.error('sendSMS error:', err)
reject(err)
} else {
console.log("Message sent. Data: ", data)
resolve(data)
}
})
})
}
const smsResponder = async (event) => {
const msg = JSON.parse(event.Sns.Message)
const msgWords = msg.messageBody.split(" ")
// Check the first word of the text message is the keyword
if (msgWords[0].toLowerCase() !== KEYWORD) return console.log('No keyword found - exiting')
// Validate zip code and get the weather
let message =''
const zipCode = msgWords[1]
if (validateZipCode(zipCode)) {
message = await getWeather(zipCode)
} else {
message = 'Invalid zip code - text me in the format "weather 00000".'
}
// Send the SMS response
var params = {
ApplicationId: process.env.ApplicationId,
MessageRequest: {
Addresses: {
[msg.originationNumber]: {
ChannelType: 'SMS'
}
},
MessageConfiguration: {
SMSMessage: {
Body: message,
MessageType: 'PROMOTIONAL',
OriginationNumber: msg.destinationNumber
}
}
}
}
Finally, getWeather.js takes a zip code and queries the OpenWeatherMap API for the weather summary. It performs some minimal processing to convert the result into a text message.
const getWeather = async function (zipCode) {
try {
// Get weather for the zip code provided
const response = await axios({
url: `${weatherURL}&zip=${zipCode}&APPID=${process.env.APIkey}`,
method: 'get',
port: 443,
responseType: JSON
})
// Build natural response
const weather = `Weather in ${response.data.name}: ${response.data.weather[0].description}, currently ${parseInt(response.data.main.temp)} degrees with a low of ${parseInt(response.data.main.temp_min)} and a high of ${parseInt(response.data.main.temp_max)}.`
console.log('getWeather response: ', weather)
return weather
} catch (err) {
console.error('getWeather error: ', err)
return 'Sorry, there was a problem with the weather service.'
}
}
Conclusion
Amazon Pinpoint simplifies handling two-way SMS to customer phones. A Lambda function can inspect incoming text messages, process the data, and send a response, all within 100 lines of code. Although this example only checks the weather one time, the functionality could be extended to any of the following tasks:
- Sending daily weather reports.
- Providing alerts for significant weather events.
- Adding additional keywords to support different types of queries, such as weather averages.
Alternatively, this flow can be used to help support order processing, appointment management, or create marketing campaigns. Adding two-way SMS provides your customers with new ways to interact with your business applications.