#BlackLivesMatter Support the Equal Justice Initiative.

How to Send SMS & MMS Messages with Twilio + Netlify Functions

Tuesday, March 31st 2020

Twilio's messaging APIs and serverless platforms like AWS Lambda work together quite well in my experience. The pricing is often literal pennies for both services and the infrastructure is dependable and scalable. There's a peace of mind knowing I'll rarely have to debug some obscure package configuration or take a project offline to upgrade the server and handle more traffic.

Truthfully, the biggest challenge is in provisioning the serverless app on AWS in the first place. It's come a long way, and there are tools that can make that process simpler. Even then it can still be a bit of a headache—particularly if all you want to do is quickly hack around with something or explore the feasability of an idea.

That's where Netlify comes in. They make setting up serverless AWS Lambda functions easier than AWS does on their own platform! In particular I've found it's much easier to setup and test my serverless functions locally using the Netlify command-line tools.

Here's an example of how to build a form that will text the NASA image of the day to the number you specify in a form using Netlify + Twilio. This demo assumes you've already setup a Twilio app and have at least a little familiarity with their API.

  • First you'll want to download and install the Netlify CLI
  • Create a new project folder and setup a TOML configuration file for your functions
  • In your functions folder make a file and call it nasa.js
  • Make an HTML file for your form and call it index.html
  • Now run netlify dev from the root folder to serve and start testing this project

In the HTML file the code for your form should look something like this:

<form id="twilioDemoForm" action="." method="POST">
  <strong>Send me a download link</strong> (demo) <br>
  <label for="number">Phone Number (U.S. Only)</label>
  <input type="text" name="number" placeholder="1-555-555-5555">            
  <br>
  <button>Send</button>
  <div id="response"></div>
</form>

This is the code that goes in our nasa.js file for our function:

/**
 * Twilio SMS Link & Platform Detection Demo
 * ===
 * George Mandis (snaptortoise.com)
 */

const querystring = require("querystring")
const phone  = require('phone');

let Parser = require("rss-parser");
let parser = new Parser();

// You'll need these from Twilio
// It's best to store them as environment variables if possible
const accountSid = TWILIO_ACCOUNT_SID; 
const authToken = TWILIO_AUTH_TOKEN;
const client = require('twilio')(accountSid, authToken);

exports.handler = function(event, context, callback) {

  if (event.httpMethod !== "POST") {
    callback(null, {
      statusCode: "200",
      body: "No POST data",
      headers: {
        "Content-Type": "text/plain"
      }
    })
    return false;
  }

  const POSTData = querystring.parse(event.body);
  
  // this check ensures it's a USA number. May not matter for you
  const phoneNumber = phone(POSTData.number); 
  
  if (phoneNumber.length < 2 && phoneNumber[1] !== 'USA') {
    const response = {
      "message": "Invalid phone number (USA only, sorry)",
      "success": false
    }

    callback(null, {
      statusCode: "200",
      body: JSON.stringify(response),
      headers: {
        "Content-Type": "text/json"
      }
    })
    return false;
  }

  /**
   * Sent the NASA Photo fo the Day via MMS
   */
  
  (async() => {
    const feed = await parser.parseURL('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss');  
    const NASAPhotoOfTheDay = feed.items[0].enclosure.url;
    const NASAPhotoOfTheDayTitle = feed.items[0].title;
    const NASAPhotoOfTheDayLink = feed.items[0].link;
    
    client.messages
      .create({
        body: `You requested an MMS demo from SnapTortoise.\n\nHere is the NASA Photo of the Day:\n\n${NASAPhotoOfTheDayTitle}.\n\nRead more at ${NASAPhotoOfTheDayLink}`,
        mediaUrl: NASAPhotoOfTheDay,
        from: '+XXXXXXXXXXX', // you'll need to register your phone # with Twilio
        to: phoneNumber
      })
      .then(message => {
        console.log(message.sid)

        const response = {
          message: `Photo sent to ${phoneNumber}!`,
          success: true
        }

        callback(null, {
          statusCode: "200",
          body: JSON.stringify(response),
          headers: {
            "Content-Type": "text/json"
          }
        })
      });

    })()
}

Note: You can also write these functions in Go. This example uses Node.js.

Here is our client-side JavaScript code to make the form asynchoronously send the contents to your function using fetch/await:

<script type="text/javascript">

const responseTextContainer = document.querySelector('#response');
const twilioDemoForm = document.querySelector('#twilioDemoForm');
  
  twilioDemoForm.addEventListener('submit', async (event) => {            
    event.preventDefault();
    // something here to keep bots form submitting  willy-nilly
    
    twilioDemoForm.querySelector('button').disabled = true;   // to prevent accidental multiple submissions
    
    const serverResponse = await fetch("/.netlify/functions/nasa", {        
    "body": "number=" + document.querySelector('input[name=number]').value,
      "method": "POST",
      "mode": "cors"
      })

    // get response
    const response = await serverResponse.json();
    
    // display the message
    responseTextContainer.textContent = response.message
      
  });

  </script>

You can see the whole thing in action here on the website for SnapTortoise, my web development company.