" width="1000" style="--opacity:1">
Sam Magura
It's common for web applications to give users multiple options for how they want to receive notifications, 2FA codes, .etc. In our previous post, we covered sending transactional email via Mailchimp's API. So now it's time to send some SMS text messages!
We'll be using Twilio for this, since it is the leading platform for sending automated texts. We've already done several blog posts using AWS Lambda , so we're going to use Azure Functions for a change. As usual, we'll write the code in TypeScript and store the Twilio API credentials in the Zero secrets manager. Then, we'll use the Zero TypeScript SDK to fetch the credentials at runtime.
🔗 The full code for this example is available in the zerosecrets/examples GitHub repository.
If you aren't familiar with Azure Functions, it is largely similar to AWS Lambda, but in Microsoft Azure rather than AWS. Like AWS Lambda, Azure Functions allows you to write a function in JavaScript, C#, Python, .etc and then bind that function to a trigger which will cause your function to run. We'll be using an HTTP-triggered function in this walkthrough, though you can also configure you function to be triggered when a new message is sent to an Azure Service Bus queue (among many other things).
One powerful feature of Azure Functions which I don't think has an equivalent in AWS Lambda, is that you can choose whether to run your functions in a serverless computing model (via a Consumption Plan) or on an App Service plan with a fixed amount of vCPU and memory (via a Premium or Dedicated Plan). The Premium and Dedicated Plans make Azure Functions suitable for long-running and memory-intensive tasks that would exceed the limits of most functions-as-a service platforms. The differences between these plans are explained here in the Microsoft Azure docs.
Zero is a modern secrets manager built with usability at its core. Reliable and secure, it saves time and effort.
Now that we've covered the background, let's begin the walkthrough.
Singing up for Twilio is easy and free. Click here to create your account.
Once you've created your account, you'll be shown the Twilio Console. Click the "Get a US toll-free phone number" on this page to get a phone number to send text messages from. We'll pass this phone number to our Azure Function as an environment variable in a future step.
At the bottom of the page you'll find your Account SID and Auth Token — these are the credentials you'll need to call the Twilio API. We will store the API credentials in Zero, so sign in to Zero and create a new project. You'll only be shown the Zero token once, so save it somewhere on your local disk. Then, click the "Create new secret" button, select Twilio from the dropdown, and paste in the Account SID and Auth Token.
It's almost time to begin coding — but first, we need to install some tools which are needed for Azure Functions development.
🔗 If anything in this walkthrough is unclear, feel free to refer to this official quickstart tutorial in the Azure docs, which my guide is based on.
To create and deploy an Azure Functions project, you'll need:
Then, follow the steps under "Prequisite check" here to verify that everything is set up properly.
You can initialize a new TypeScript Azure Functions project with the command
This will create a directory containing a package.json
, a tsconfig.json
, and some JSON configuration files that are specific to Azure Functions. cd
into the new directory and run npm install
.
Next, we'll use the Azure Functions Core Tools to scaffold an HTTP-triggered function for us:
You can check out the code that got generated by opening SendSms/index.ts
. I updated the "methods"
array in my function.json
to only include "post"
, since GET requests should not trigger side effects like sending a text message.
Let's run the project to confirm that our function is able to respond to HTTP calls. To do this, execute
followed by
This should print the message returned by the function to your console.
Now let's update our function to send an SMS text message when it receives an HTTP POST request. To do this, we'll leverage the Twilio npm package and the Zero TypeScript SDK . You can install these with:
To be able to import the twilio
package in our code, we need to add the following line to the compilerOptions
key in tsconfig.json
:
Unless you're working with a legacy TypeScript codebase, you should always have esModuleInterop
enabled. Without it, you'll get compilation errors when importing from many popular JS libraries, even if your import
statement exactly matches what is shown in the library's README.
In a real application, you would likely have multiple functions that need to send text messages. So it makes sense to initialize the Twilio API client in a utility function that can used throughout the codebase. This function will use the Zero SDK to fetch the Account SID and Auth Token from the cloud, and then pass those in to the constructor of the Twilio
client. I placed the code for this in a file called utils/getTwilioClient.ts
. Here it is:
Let's return to the code for our main function, which is in SendSms/index.ts
. The core piece of code which actually sends the SMS text message is
To make this work, we just need to assemble all the pieces. First, we'll set the twilioNumber
and myNumber
variables from environment variables. Then, we'll get a Twilio API client using the getTwilioClient
function from the previous section. Finally, we call twilio.messages.create
and return an HTTP response with the message's ID and status. This response data is just to help with debugging in case something goes wrong.
All together, the code for this is as follows:
Before running the project, let's set the TwilioNumber
and MyNumber
variables in local.settings.json
. You could also set these variables when running npm start
— though putting them in local.settings.json
can be more convenient when you're restarting the application frequently during development.
The TwilioNumber
and MyNumber
variables should be placed under the "Values"
key as follows. For MyNumber
, you should enter your actual phone number.
You can run the Azure Functions project locally via
Then issue a POST request to the function with curl like we did before:
If it worked, you'll receive a text message!
The only thing left to do is to deploy our code to Azure. We'll create the Azure resources through the CLI, but you can also do most of these steps through the Azure Portal.
Create a resource group:
Replace <REGION>
with the name of an Azure region, like eastus2
.
Azure Function Apps require a storage account. You can create one with:
The storage account name must be globally unique and consist of lowercase letters and numbers only.
Create a Function App with:
The Function App's name is used in its URL, so it needs to be globally unique.
Build your Azure Functions project for production by running
Now use the Azure Functions Core Tools to deploy your local project to the Function App:
The local.settings.json
is not deployed with your app, so we need to add the TwilioNumber
and MyNumber
variables as app settings in the Function App's configuration. You can do this by navigating to the Function App in Azure Portal and clicking "Configuration" in the menu on the left. Add the two variables via the "New application setting" button and click "Save".
To test that the function still works now that it's hosted in Azure, run
where <APP_NAME>
is the name of your Function App.
If you created any Azure resources, be sure to clean them up when you are done. You can do this by running
In this article, we created an Azure Functions application that sends a SMS text message via Twilio, with the Twilio API credentials fetched at runtime from the Zero secrets manager. At this point, there's two areas in which you can continue your exploration. The first would be to delve deeper into the APIs provided by Twilio. For example, you may be interested in creating an interactive chat experience via Twilio Conversations , or adding voice notifications to your app with Twilio Voice .
Another direction you can explore is building out your application's infrastructure in Azure. Azure Functions pairs very well with Azure Static Websites , which is great for hosting single-page application frontends. For persistence, Azure SQL is the simplest option, while Azure Cosmos DB offers better scalability for apps that see extremely heavy usage.
In this post, we'll use Pulumi to define our application's Azure infrastructure using clean and declarative TypeScript code.
Integrate with the Gmail Push Notifications API to enable your app to intelligently respond to new emails.
Zero is a modern secrets manager built with usability at its core. Reliable and secure, it saves time and effort.