How to send text messages from your static site using Netlify, Twilio and serverless functions

Stefan Judis - Apr 1 '19 - - Dev Community

It’s exciting times to be a Frontend developer. With the rise of modern technologies such as serverless functions, Frontend engineers can do things that usually only backend engineers could do. This entails deploying scalable sites, sending emails, or creating HTTP endpoints. Due to the power of new service providers and countless APIs, building performant applications has become rather a game of connecting dots than building everything from the ground up.

In this article, I want to share how you can create and deploy a scalable static site on Netlify and how you can use serverless functions to send text messages using Twilio.

Screenshot of 8-bit-revolution.netlify.com

You can see the final result on 8-bit-revolution.netlify.com. You can go there and tell me how much you love the good old 8-bit style via SMS. If you want to set-up a site similar to “8-bit revolution” you can have a look at the project’s readme. It includes a one-click install button with which you can create an SMS sending website yourself. In the setup, you can configure the telephone numbers of the recipients and the text you want to send.


If you want to follow along instead and want to understand how it works, what you'll need to get started are:

Why static?

HTML powers the web and serving HTML statically has a few advantages. Static sites are more secure because there is less computation involved. The HTML is pre-generated and files can be served 1:1 one from the server which reduces the number of attack vectors. Additionally, static sites are cheap. GitHub pages and other service providers mainly offer static site hosting for free. And lastly, static sites are scalable. Serving static files doesn’t need much computation power on the server-side and in case you need it, you can quickly put a CDN in front of your site to be ready to serve your millions of visitors.

Writing hundreds of plain HTML pages by hand can be cumbersome, though. This is why build tools and static site generators have become a common practice. These tools combine templates with Markdown files or API data to generate the static HTML.

This results in the need for a more complex setup. A server needs to generate the HTML and then upload the HTML to your static site host. This is where continuous integration systems (CI) like TravisCI come into play. These services allow you to rebuild your site when you push code updates or when the content was updated.

Netlify is your CI, CDN and serverless platform

Netlify is a fairly new service that solves this problem of increased complexity. You define a directory and a build script and they take care of the creation of your site and put it on a global content delivery network (CDN). You can use additional features like serverless functions or forms to enrich your static site with additional functionality – all included in one platform.

Sounds good? Let’s do it!

Create your static website

Create a new directory and include a dist subdirectory. dist will hold all the files that should be deployed by Netlify. You can place a barebones index.html file like the one below in there and you’re good to go.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>8-bit revolution</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="theme-color" content="#fafafa">
</head>
<body>
  <p>8-bit rocks!</p>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Additionally, you have to define a command that should be run when you deploy the site. Initialize a new npm project in the project directory.

npm init --yes
Enter fullscreen mode Exit fullscreen mode

The --yes flag allows you to skip the questionnaire that usually comes with npm when you create a new project. The command creates a new package.json which is the configuration file for Node.js projects. Let’s add a dummy build command that only echoes a log message to the scripts property of the included JSON object.

Your package.json should look similar to the following:

{
  "name": "8-bit-revolution-tut",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "echo \"Building your new static site!\""
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/stefanjudis/8-bit-revolution-tut.git"
  },
  "keywords": [],
  "author": "stefan judis <sjudis@twilio.com>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/stefanjudis/8-bit-revolution-tut/issues"
  },
  "homepage": "https://github.com/stefanjudis/8-bit-revolution-tut#readme"
}
Enter fullscreen mode Exit fullscreen mode

Note: the name, repository, author, bugs, and homepage properties will be different and tailored to your user and project.

You can run all defined script properties in your terminal using npm run followed by the property name. Executing npm run build will log “Building your new static site!” to the terminal. That’s not much yet and you will add more functionality to this script later in this tutorial.

Initialize git in your project root and push it to a new GitHub repository.

After committing and pushing these two files to GitHub you’re ready to deploy the site. Head over to Netlify, login, and click “New site from Git” to connect your repository with Netlify.

Site overview of Netlify admin area

After connecting with GitHub you’ll be able to choose your newly-created repository. Netlify will ask you about the branch and directory which should be deployed and, additionally, which script you want to execute during the deployment process. In this case, it will be master, dist and npm run build.

New site dialog to define build script and folder in Netlify admin area

Press “Deploy site” and see your static site making its way into the internet. The deploy will only take a couple of seconds and will be available at a random subdomain like frosty-mclean-c6c41c at Netlify. You can change this subdomain or connect your own domain if you want to, too. 🎉

First successful deploy

Congrats! With pressing “deploy site” you set up a new deployment pipeline.

The cool thing about the Netlify and GitHub connection is that whenever you push new code to GitHub, Netlify will get notified via webhooks and automatically deploy your site. It is also ready to accept requests from any system you use so that you can trigger rebuilds after different events such as updating content in a remote content management system.

Add a form to allow user inputs

For the case of sending messages, static HTML is not enough. You have to add a way to allow for user input like pressing a button or entering data. Luckily, Netlify provides built-in form handling. Replace the “8-bit rocks!” paragraph with the following form.

<form name="contact" class="form" method="POST" data-netlify="true">
      <h1>Wanna spread the word?</h1>
      <button class="btn" type="submit">Send messages!</button>
</form>
Enter fullscreen mode Exit fullscreen mode

The form has to include a data-netlify=”true” attribute. This tells Netlify that you want this form submission handled by their servers. Commit and push the code. Wait for the deploy to finish and voila – you can now handle form submissions!

First deploy including a form and a button

The data of all submitted forms will be available in the Netlify admin area under “Forms”. This makes it perfect for collecting data from contact forms and more.

Netlify form submission success dialog

At this stage, when you submit the form, Netlify will show you a generic success message that tells you it received the form submission. You can change that by defining a page that should be redirected to with the action attribute on the form element.

I prefer to submit the form via JavaScript to avoid the redirect.

Use Ajax to submit forms

With the implementation of the globally-available fetch method, you can make requests directly from within the browser without needing to add any dependencies.

Before you start implementing the Ajax functionality, add two more HTML elements to the page. These elements will indicate success or error of the form submission request.

<p class="successMsg" role="alert" hidden>Messages sent...</p>
<p class="errorMsg" role="alert" hidden>Something went wrong...</p>
Enter fullscreen mode Exit fullscreen mode

Add an inline script element to the bottom of the page. Using native DOM methods you can listen for the submit event of the form and apply custom functionalities like making an AJAX request.

<script>
  const form = document.querySelector('form');
  form.addEventListener('submit', async event => {
    event.preventDefault();

    // disable button to prevent multiple submissions
    form.querySelector('button').disabled = true;

    // make the request to submit the form
    try {
      const response = await fetch('/', {
        method: 'post',
        headers: {
          'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
        },
        // parse and submit all included form data
        body: new URLSearchParams(new FormData(form)).toString()
      });

      // if it was successful show success message
      if (response.status === 200) {
        document.querySelector('.successMsg').hidden = false;
      } else {
        document.querySelector('.errorMsg').hidden = false;
      }
    } catch (e) {
      console.error(e);
    }
  });
</script>
Enter fullscreen mode Exit fullscreen mode

The fetch method returns a promise. If your browser support allows it you can use an async function as a submit handler to avoid callbacks and then-chains combined with await and a try/catch.

Form submission with ajax enabled success message

Push the code and wait for the deploy. When you test the form submission you’ll see that the form submission works and that the site shows you a success message but it is not sending any messages yet.

Before you implement the SMS sending part let’s bring the 8-bit style back into the site. Copy the styles from the example repository and paste them into a new file styles.css in your dist directory. Additionally, add a link to a Google font which is referenced in the just-pasted styles to the index.html.

<head>
  <meta charset="utf-8">
  <title>8-bit revolution</title>
  <!-- reference the new stylesheet -->
  <link rel="stylesheet" href="/styles.css" />
  <!-- load Google Font to get get a nice 8-bit font -->
  <link
    href="https://fonts.googleapis.com/css?family=Maven+Pro|Yrsa|Press+Start+2P"
    rel="stylesheet"
  />
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="theme-color" content="#fafafa">
</head>
Enter fullscreen mode Exit fullscreen mode

Deployed site with 8-bit style

Not too bad! Now that your site looks nice and “8-bit’y” let’s implement the SMS functionality.

Reacting to submitted forms using serverless functions

When a user submits the form nothing happens except Netlify stores the form data. To implement the SMS-sending functionality you can use serverless functions provided by Netlify. It’s recommended to use the netlify-lambda npm package to create these functions. Install the package via the command line.

npm install --save netlify-lambda
Enter fullscreen mode Exit fullscreen mode

netlify-lambda inlines all the dependencies included in your functions so that they become executable in the Netlify environment. Create a directory called functions and add a JavaScript file submission-created.js. Netlify follows naming conventions to run functions after certain events. Additionally, you can give your function file a name that is not included in the list to spin up new HTTP endpoints, too.

// submission-created.js
exports.handler = function(event, context, callback) {
  console.log('Submission created!')
};
Enter fullscreen mode Exit fullscreen mode

Before you can generate the function you have to define the directory in which it should be stored. Create a netlify.toml file inside of your project root. Define the [build] property and include the configuration that your generated ready-to-run functions will be stored in the .functions directory.

[build]
  functions = ".functions"
Enter fullscreen mode Exit fullscreen mode

Adjust your package.json to build your serverless functions during deploys.

{
  "scripts": {
      "build": "netlify-lambda build functions"
    } 
}
Enter fullscreen mode Exit fullscreen mode

When you run npm run build locally it generates a new .functions directory including the functions that are ready to run on Netlify. The new .functions directory includes generated code and might not be worth checking into git and pushing to GitHub. Make sure to create a .gitignore file that includes the generated directory.

# .gitignore
# generated functions
.functions
Enter fullscreen mode Exit fullscreen mode

With these adjustments, you can commit and push to GitHub. Netlify will automatically deploy the website including the function that runs when someone submits the form.

When you submit the form to tell the world about 8-bit and get to the function log in Netlify you will see the “Submission created!” message.

Function log in Netlify admin area

Sending SMS’s within a serverless function

Your function is ready to react to form submission at this point and you can start sending text messages. To send messages with Twilio you have to define sensitive information like your account SID, your account token, and the phone numbers of the people you want to send messages to. Make sure these don’t make it into a public git repository.

Install the dotenv package using npm install --save dotenv. It allows you to read configuration variables from a .env file in the root of the directory and makes them accessible in your serverless function via process.env. Create the .env file, include it in your .gitignore, and define the following values, replacing them with your own:

TWILIO_ACCOUNT_SID = “YOUR-TWILIO-ACCOUNT-SID”
TWILIO_AUTH_TOKEN = “YOUR-TWILIO-AUTH-TOKEN”
BOT_NUMBER = “YOUR-BOUGHT-TWILIO-NUMBER”
BOT_MESSAGE = "YOUR-MESSAGE"
CONTACT_NUMBERS = "NUMBERS-THAT-SHOULD-RECEIVE-A-MESSAGE"
Enter fullscreen mode Exit fullscreen mode

To get all these values, log into Twilio. You can find your account SID and auth token in the dashboard. The auth token is very sensitive information because it can grant the same access as the current user has. I recommend to catch up on some best practices to keep your auth token secure before proceeding.[a]

Twilio console including account sid and token

Next, you need to buy a Twilio phone number. Make sure you buy one with SMS capabilities.

Twilio Console buy a number dialog

After you purchased a number you can define the message that will be sent and the numbers of your recipients in the configuration file yourself.

Your .env file should look like this then:

TWILIO_ACCOUNT_SID = "AC..."
TWILIO_AUTH_TOKEN = "a8..."
BOT_NUMBER = "+4915735982595"
BOT_MESSAGE = "8-bit rocks!"
CONTACT_NUMBERS = "+491761234567;+49170987654"
Enter fullscreen mode Exit fullscreen mode

Adjust your function to access the above-defined values.

// submission-created.js
// load the env file and make values accessible via process.env
require('dotenv').config();

const {
  TWILIO_ACCOUNT_SID,
  TWILIO_AUTH_TOKEN,
  CONTACT_NUMBERS,
  BOT_NUMBER,
  BOT_MESSAGE
} = process.env;

exports.handler = function(event, context, callback) {
  console.log('Submission created!')
  // the logic for sending the message will go here
};
Enter fullscreen mode Exit fullscreen mode

Install the Twilio helper library on the command line.

npm install --save twilio
Enter fullscreen mode Exit fullscreen mode

With the Twilio helper library at hand, you can now send text messages. Replace the log message and add the following.

// submission-created.js

// ...
// 👆 dotenv and process.env handling as above

// initialize the helper library client
const client = require('twilio')(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN);

exports.handler = function(event, context, callback) {
  Promise.all(
    // split the string of several messages into single numbers
    // send message to each of them
    CONTACT_NUMBERS.split(';').map(num => {
      return client.messages.create({
        from: BOT_NUMBER,
        to: num,
        body: BOT_MESSAGE
      });
    })
  )
    .then(() => callback(null, { statusCode: 200, body: 'Created' }))
    .catch(e => {
      console.log(e);
      callback(e);
    });
};
Enter fullscreen mode Exit fullscreen mode

To run your function locally add a serve command to the package.json to spin up a local development server.

{
  "scripts": {
      "serve": "netlify-lambda serve functions"
    } 
}
Enter fullscreen mode Exit fullscreen mode

The above netlify-lambda command will build and generate your function and open an HTTP endpoint. If you run npm run serve and then open http://localhost:9000/submission-created it will run your function and send SMS messages. 🎉

Commit and push the new function and wait for the deploy. But wait… when you try to send an SMS by pressing the button on the deployed Netlify site you’ll discover that it doesn’t work yet. Remember that you put your .env file into .gitignore?

Working with .env files in combination with environment variables is a common practice to avoid leaking credentials. Using dotenv you can make sure your application works with both defined environment variables and .env configuration files. Locally dotenv reads the .env file and places values in process.env. In production – you have to define these environment variables in process.env yourself. This way you don’t have to place credentials in public places.

You can define environment variables in the Netlify admin area under “Build”, “Build settings” and “Build environment variables”.

Environment variables defined in Netlify admin UISave the values and trigger a new build. Now hitting the button will show all your recipients that you love 8-bit. 🎉

You can find the final function implementation on GitHub.

Conclusion

In this tutorial, you learned how to deploy static sites with Netlify, enrich your sites using serverless functions, and send an SMS using Twilio.

Serverless functions are perfect connectors in the API-driven world we live in. They can connect services, accept webhooks, or even respond to SMS messages.

Let me know what messages you send. You can find the complete code on GitHub. It includes a more sophisticated form submission flow which you might want to check out.

If you have any questions please don’t hesitate to reach out on the following channels:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .