How to Send Daily SMS Reminders Using C#, Azure Functions and Twilio

January 24, 2017
Written by

twilioazurereminders

Are you remembering to keep up with your New Year’s Resolutions? Using C#, Azure Functions and Twilio we’ll build a service that delivers daily SMS reminders to help keep up with those new goals.

Recipe

Here’s a list of the things we’ll use in the creation of our reminder service:

Set up your accounts and install any of the software you don’t have before moving on.

Structuring the Reminder Service

Azure Functions make it easy to quickly create the type of service we’re building. It’s “serverless” which means we don’t have to worry about web server architecture. Instead, we can just focus on the small bits of code or “functions” that make our service work.

We’ll create one function that handles receiving SMS messages from Twilio. When it receives a request from Twilio it will create a record in an Azure Table if the text message contains “subscribe”. A second function will fire once a day to send an SMS reminder to each number in the Azure table.

Simple enough? Let’s get started.

Handling Subscription Requests

Create a new Project in Visual Studio 2015 and search the installed templates for “Azure Functions”:


Name your project DailySmsReminders and click OK.

Before we add our functions, let’s configure the app settings. Open appsettings.json and replace its contents with the following:


{
  "IsEncrypted": false,
  "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "AzureWebJobsDashboard": "",
        "TwilioAccountSid": "[Your Twilio Account Sid]",
        "TwilioAuthToken": "[Your Twilio Auth Token]",
        "TwilioPhoneNumber": "[Your Twilio Phone Number]",
        "ReminderMessage": "Are you keeping up with your resolutions?"
  }
}

The UseDevelopmentStorage=true setting on line 4 makes it so that our project will use the Azure Storage Emulator any time we make a request to use Azure storage. Make sure to replace the Twilio credentials with your actual values.

Next, right-click on the DailySmsReminders project and select Add->New Azure Function…:


The dialog that pops up contains a lot of options. These represent the various ways an Azure function can be triggered. For instance, a function can be triggered when something is added to a queue using a QueueTrigger. Read more about what types of triggers are available here. Our first function will need to run when an HTTP request is made to it. The HTTPTrigger - C# option is perfect for this. Select this option, name the function InboundSmsTrigger and click Create:

Edit InboundSmsTriggerproject.json to add a dependency on Twilio.Twiml which we’ll use to return TwiML to Twilio when a text message comes in:

{
  "frameworks": {
    "net46": {
      "dependencies": {
        "Twilio.Twiml": "3.5.0"
      }
    }
  }
}

The InboundSmsTriggerfunction.json file is used to configure bindings for our function. The two existing bindings specified in the file correspond to the inbound HTTP request and the return from the function. We need to add a binding for the Azure Table we’ll use to store our records. Edit function.json so that it looks like this:


{
  "bindings": [
    {
      "authLevel": "function",
      "name": "req",
      "type": "httpTrigger",
      "direction": "in"
    },
    {
      "tableName": "Reminder",
      "connection": "",
      "name": "table",
      "type": "table",
      "direction": "in"
    },
    {
      "name": "$return",
      "type": "http",
      "direction": "out"
    }
  ],
  "disabled": false
}

In the highlighted lines we’ve added a table binding with a tableName of Reminder and set the connection for the table to the Azure Storage Emulator.

The function’s code can be found in InboundSmsTriggerrun.csx. Delete what’s there and replace it with the following code:


#r "Microsoft.WindowsAzure.Storage"
#r "System.Runtime"

using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
using System.Net;
using System.Text;
using Twilio.TwiML;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, CloudTable table, TraceWriter log)
{
}

public class Reminder : TableEntity
{
}

On lines 1-2 we pull in references for Microsoft.WindowsAzure.Storage and System.Runtime. These are special case external assemblies that can be referenced in this way. There’s a full list in the documentation.

Line 10 adds a CloudTable table parameter to the function that corresponds to the table binding we added to function.json. This gives us access to the Reminder table in Azure Storage.

The Reminder class at the bottom is a TableEntity subclass that we will use to store records in Azure Table Storage. The class contains a PartitionKey which we’ll always set to Reminders and a RowKey which we’ll use for storing the user’s phone number to send reminders to later.

The HTTP request from Twilio will contain form data in the body of the request that contains the parameters such as Body and To/From phone numbers. Let’s add some code to parse those into a Dictionary and store the body of the message and the phone number the text message was sent from:

var data = await req.Content.ReadAsStringAsync();
    
var formValues = data.Split('&')
    .Select(value => value.Split('='))
    .ToDictionary(pair => System.Uri.UnescapeDataString(pair[0]), 
                  pair => System.Uri.UnescapeDataString(pair[1]));

var body = formValues["Body"];
var phoneNumber =formValues["From"];
var response = new TwilioResponse();

We also created a TwilioResponse object that will be used to return TwiML to Twilio.

If the user sends a message that contains anything other than subscribe we’ll want to send back a message letting them know about the keyword. Let’s set that up by adding the following code:


if (body.Trim('+').ToLower() == "subscribe")
{
    // User is subscribing
}
else
{
    response.Message("Welcome to Daily Updates. Text 'Subscribe' receive updates.");
}

When we parsed the Body value using System.Uri.UnescapeDataString. Any spaces in the string will be left as + signs. The highlighted line trims any + signs from the beginning and end of the body value, makes it lowercase, and compares it against subscribe.

The bulk of the work for the function occurs when the text message body is subscribe. Add this code to that block:


TableOperation operation = TableOperation.Retrieve<Reminder>("Reminders", phoneNumber);
TableResult result = table.Execute(operation);

if (result.Result != null)
{
    response.Message("You already subscribed!");
}
else
{
    response.Message("Thank you, you are now subscribed. Reply 'STOP' to stop receiving updates.");
    var insertOperation = TableOperation.Insert(
        new Reminder()
        {
                    PartitionKey = "Reminders",
                    RowKey = phoneNumber
        }
    );

    table.Execute(insertOperation);
}

Lines 1 and 2 look in the Reminder table for an entry that corresponds to the PartitionKey of Reminders and the RowKey of phoneNumber. If the result exists then we set the response message in line 6 to let the user know they’re already subscribed. If they’re not subscribed already then on line 10 we let the user know they are subscribed and the rest of the code in this block executes an insert operation to add their phone number to the table.

All that’s left to do is return an HttpResponse from the function. We’ll return the response TwiML as application/xml using UTF8 encoding with the following code:

return new HttpResponseMessage
{
    Content = new StringContent(response.ToString(), System.Text.Encoding.UTF8, "application/xml")
};

Now it’s time to test this function. First we need to start up the Azure Storage Emulator. Search in the Start menu for Azure Storage Emulator and run it. This will open a command prompt window that runs a command to start it:

Once that’s running you can build and run the Azure Functions solution. On first run, it will ask you to install the Azure CLI tools. Do this and then run the project again:

Just under the cool Azure Functions ASCII art you should see a localhost URL with a port number. You’ll need to use something like ngrok to forward this localhost port to the Internet so that Twilio can make an HTTP request to it. You can use this guide if you’re unsure how to set this up. A quick and easy way to get ngrok running from Visual Studio is this very handy ngrok extension.

Head over to your Twilio phone number and configure the A Message comes in webhook to point to https://[your ngrok url]/api/InboundSmsTrigger and click Save.


Test the inbound trigger by sending SMS messages. Try sending something that’s not subscribe then send subscribe and finally try subscribing again. Now that we have subscriptions, let’s send the reminders.

Don’t Forget, Send Daily SMS Reminders!

Add another Azure Function to the project. This time select TimerTrigger - C# and name the function SendSmsReminders. A timer trigger will fire according to a schedule determined by a cron expression in the function.json file. The cron expression has six fields: {second} {minute} {hour} {day} {month} {day of the week}. For example, a cron expression that will trigger at 10:00:00 AM in the Eastern US time zone every day would look like this: 0 0 15 * * *. Modify SendSmsRemindersfunction.json to contain this cron expression and also add a table binding like we did in the previous function:

{   
  "bindings": [
    {
      "name": "myTimer",
      "type": "timerTrigger",
      "direction": "in",
      "schedule": "0 0 15 * * *"
    },
    {
      "tableName": "Reminder",
      "connection": "",
      "name": "table",
      "type": "table",
      "direction": "in"
    }
  ],
  "disabled": false
}

Next, add a Twilio dependency to SendSmsRemindersproject.json:

{
  "frameworks": {
    "net46": {
      "dependencies": {
        "Twilio": "4.7.2"
      }
    }
  }
}

Now we’re ready to send the reminders. Open up SendSmsRemindersrun.csx and replace the contents with this code which references the Twilio and Azure Storage assemblies and adds the CloudTable binding to the parameter list:

#r "Microsoft.WindowsAzure.Storage"

using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
using System;
using Twilio;

public static void Run(TimerInfo myTimer, CloudTable table, TraceWriter log)
{
}

public class Reminder : TableEntity
{

}

Inside the Run function we’ll start by setting up a TwilioRestClient:

var accountSid = Environment.GetEnvironmentVariable("TwilioAccountSid");
var authToken = Environment.GetEnvironmentVariable("TwilioAuthToken");
var twilio = new TwilioRestClient(accountSid, authToken);

Next, construct a query over the Reminder table that matches the Reminders PartitionKey. This will return all of the users that have subscribed:

var query = new TableQuery<Reminder>().Where(
    TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "Reminders")
);

Now that we have all of the entries, loop through each one and send the SMS reminder using the TwilioRestClient:

foreach (Reminder reminder in table.ExecuteQuery(query))
{
    var message = twilio.SendMessage(
        Environment.GetEnvironmentVariable("TwilioPhoneNumber"),
        reminder.RowKey,
        Environment.GetEnvironmentVariable("ReminderMessage")
    );
}

To test this out you’ll want to change the cron expression in function.json to a time a few minutes in the future and then run the project. You should receive an SMS containing the ReminderMessage when the cron expression evaluates to the time you set. Now you won’t lose track of those resolutions!

What’s Next?

Now that you know how to use C#, Azure Functions  and Twilio to send daily SMS reminders, try some of these things as next steps:

I’m really excited to see what you come up with using these technologies. Let me know what you build by hitting me up on Twitter @brentschooley.