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.

The SMS weatherbot responds to a request.

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:

Architecture diagram for the weatherbot

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.

Configure features

5. Select Enable the SMS channel for this project, and choose Save changes.

Set up SMS

6. Choose Settings, SMS and voice.

SMS and voice

7. Under Number settings, choose Request long codes.

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.

Confirmation page

8. At the top of the page, choose All projects, and note the Project ID. You need this in the next section.

All projects

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:

git clone https://github.com/jbesw/aws-serverless-sar-pinpoint-weather-bot

git clone

2. Create a bucket for the deployment (specify a unique bucket name):

aws s3 mb s3://your_bucket_name

Change into the cloned directory:

cd .\aws-serverless-sar-pinpoint-weather-bot\

3. Run the AWS SAM build process and create the AWS SAM package:

sam build
sam package --output-template-file packaged.yaml --s3-bucket your_bucket_name

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.
sam deploy --template-file packaged.yaml \
  --stack-name myWeatherBot
  --capabilities CAPABILITY_IAM
  --region us-east-1
  –-parameter-overrides APIkey=<<yourAPIkey>> ApplicationId=<<yourApplicationId>>

After running these commands, the console shows the following message:

Successfully created/updated stack – myWeatherBot.

sam deploy

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.

SNS topic

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.

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.

Incoming message destination

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.