Creating a Emergency Outage IVR Response in Twilio Studio

December 03, 2021
Written by
Reviewed by
Abby Ford
Twilion

Article Header

If you are part of a team that ensures the smooth operation of a contact center, you may be tasked with planning for the unknown. Surprises or rapidly changing situations are times when the value of a cloud-based voice solution shows through. In the case of an emergency — such as a weather-related closure, a facilities problem, or a personnel challenge — you may need to remotely activate a temporary change to your voice responder.

In this article, I will use Twilio’s IVR solution to create an emergency outage message that can play for incoming callers, notifying them of an unmanned queue. This message can save customers time and frustration and give you space to ensure the health and safety of your team. If you follow along with me through the steps I present here, you’ll have a working outage message for your selected Twilio phone numbers.

Tutorial prerequisites

To code along with this post you'll need a free Twilio account; sign into an existing account or sign up here.

In this blog we will use the following services:

Flow from an inbound call to a number, a Studio Flow, a Function, a Sync, routing to an alert message or Flex agentund call flow

In this scenario, a call comes into a Twilio number and goes through a Studio Flow. The Flow runs a Function that reads a Sync Map to determine if there is an outage for a particular call center location. If there is no outage, the call can proceed. If there is an outage, an alert will play to the customer and the call will conclude.

SYNC

To set up Sync to use a Map to store our contact center outage data, I used the following steps. Click on Explore products, scroll down to the Developer Tools section and select Sync:

 

An image of the developers tools accessible through Twilio Console.

Note: You can pin Sync and Functions to your Navigation Pane for future use (by clicking on the pin in the Developer Tool box in the Sync section).

On the Get started with Sync page and click View Sync Services:

An image of the sync services feature in the Twilio Console.

Select Create New Sync Service:

An image of how to create a new sync service in the Twilio Console

Give your new Service a name. I used EmergencyOutage. Click Create:

An image of how to create a name new sync service in the Twilio Console
 

You have now created your service. Take note of the Sync Service SID as you will need this later. Click on Maps from the navigation menu:

An image of the sync services SID display in the Twilio Console

Select Create new Sync Map:

An image of how to create a new sync map in the Twilio Console

Give your new Sync Map a name. I used Outages. Click Create:

An image of how to name a new sync map in the Twilio Console

You have now created your Sync Map. Take note of the Sync Map SID which you will need later for the Function. Click on the name of your Map (Outages):

 

An image of how to see the SID of Sync Map in the Twilio Console

Click on Create New Map Item:

An image of how to create a new Sync Map item in the Twilio Console.

In the new item, the Sync Map Key is the physical location or identifier where your agents work; this is a name that makes sense to your environment and is typically the place affected by the outage. In the data field, set a JSON object to be {"Enabled": false} as you want the call center location to be on normal operation (i.e. no emergency).

An image of the creating a new sync map item, with a focus on setting the data field as {"Enabled": false}

Note: The Sync Map Key is case-sensitive and you will need it for the Parameters of your Function in your Studio Flow in the next steps.

You have now created your new Sync Map item:

An image of the sync map details screen after it has been saved

This sets up your first call center Sync Map to be under normal operation.

Function readUpdateOutage

In this section you will outline the Function you need to read your newly created Sync Map. This function will also be used in later steps. I will call it readUpdateOutage as that is its intended purpose. In the Navigation Pane expand Functions and select Services:

A screenshot of the Navigation Pane, with a focus on selecting Services under the Functions headline.

 

 Click on Create Service:

An image of the Create Service screen in the Twilio Console

Give your new Service a name. I used Emergency Outage. Click Next:

NaAn image of the "Name your service" portion of the Twilio Console. me your Service screenshot

Click on Add + and select Add Function:

An image of the add function button in the Twilio Console

Enter your Function path. I used readUpdateOutage as that is relevant to what the Function does:

 

An image of adding a function path called readUpdateOutage

Leave the Function as Protected, and paste in the following code:

exports.handler = function (context, event, callback) {
 var moment = require('moment'); // require
 const client = context.getTwilioClient();
 const syncServiceSid = process.env.syncServiceSid;
 const mapId = process.env.mapId;
 const operation = event.operation || ''; // Set operation to Parameter, use blank if not provided
 const location = event.location || ''; // Set location to Parameter, use blank if not provided
 const outageMessage = event.outageMessage || ''; // Set outageMessage to Parameter, use blank if not provided
 const from = event.from || ''; // Set from to Parameter, use blank if not provided
 const outageDate = event.date || ''; // Set outageDate to Parameter, use blank if not provided
 function readSyncMap() {
       client.sync
           .services(syncServiceSid)
           .syncMaps(mapId)
           .syncMapItems(location)
           .fetch()
           .then((sync_map) => {
               console.log(JSON.stringify(sync_map.data));
               let result = sync_map.data
               if(sync_map.data.Enabled == true){
                 console.log("checking if yesterday") //Check when the message is supposed to start.
                 let startDate = moment(sync_map.data.Begins);
                 if(!moment(startDate).isSameOrBefore(moment(), "day")){ //If the message is for the future, set enabled to false
                 console.log(startDate,moment(startDate), moment() )
                   result.Enabled = false
                   console.log(result)
                 };
               }
               return callback(null, result);
           })
           .catch((error) => {
               console.log(error);
               return callback(error);
           });
   }
 
 function updateSyncMap() { //Update the Map with the new Message, Location, Enabled = true and the number of the SMS who last updated it
   let data = {
         "Enabled": true,
         "Message": outageMessage,
         "Begins": outageDate,
         "Number": from
       }
   client.sync.services(syncServiceSid)
     .syncMaps(mapId)
     .syncMapItems(location)
     .update({
       data: data
     })
     .then((sync_map) => {
         console.log(JSON.stringify(sync_map.data));
         return callback(null, sync_map.data);
     })
     .catch((error) => {
         console.log(error);
         return callback(error);
     });        
 }
 
 function removeOutageSyncMap() { //Update the Map with the Enabled = false and the number of the SMS who last updated it
 
   let data = {
         "Enabled": false,
         "Number": from
       }
   client.sync.services(syncServiceSid)
     .syncMaps(mapId)
     .syncMapItems(location)
     .update({
       data: data
     })
     .then((sync_map) => {
         console.log(JSON.stringify(sync_map.data));
         return callback(null, sync_map.data);
     })
     .catch((error) => {
         console.log(error);
         return callback(error);
     });        
 }
 
 // Test run function via passed in parameter
 switch (operation) {
   case 'read': //Check read operation, run readSyncMap function
     {
       readSyncMap();
     }
     break;
   case 'add': //Check add operation, run updateSynMap function
     {
       updateSyncMap();
     }
     break;
   case 'remove': // Check remove operation, run removeOutageSyncMap function
     {
       removeOutageSyncMap();
     }
     break;
   default: // If no valid operation is passed, run this to avoid Function errors.
     console.log('Please pass in the function name to execute');
     console.log('Example: https:/x.x.x.x/<path>?Function=createSyncDocument');
     return callback(
       'missing URL query parameter - Example: https:/x.x.x.x?Function=createSyncDocument'
     );
 }
};

This code reads or updates the Sync map. It reads to check if there is an outage and updates the Sync Map to add or cancel an outage.

Click on Save:

An image of showing how to save a new function path

The Function is still not ready to deploy. You need to add the Environment Variables first. Select Environment Variables from the Settings section as shown above. Enter syncServiceSid as the Key, and use the noted Sync Service SID from earlier (in the format of ISXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX) then click Add:

An image showing how to create an environment variable for the function. Here we are creating the syncServicesSid
 

Enter mapId as the next Key and use the noted Sync Map Id from earlier (in the format of MPXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX) and click Add:

An image showing how to create an environment variable for the function. Here we are creating the mapId.

Note: Keep the Add my Twilio Credentials (ACCOUNT_SID) and (AUTH_TOKEN) to ENV ticked for use in the Function.

For this Function you will use a Module called Moment to make it easier to determine dates for your outages. You need to add this Module to Dependencies. Click on Dependencies in the Settings section:

An image showing where to click to create a dependency in the function
 

Enter moment as the Module, leave the Version as blank, and click Add:

 

A screenshot showing how to add a moment dependency to the function

And now you are ready to Deploy All:

An image showing how to deploy all code to the function

Your new Function is ready to be used in your Studio Flow.

Demo Flow

The following Studio Flow is a simple demo to test your outage as read from your Sync Map. The first step is your Check Emergency Function to readUpdateOutage. It requires some Parameters to perform properly. The Parameters include location which should be set to your call center physical location or identifier (which will match the Sync Key we created earlier). The second step is your operation for the Function to perform, which is set to read. The EmergencySplit step checks the results of the Function to determine if there is an outage: 

 

An image of the emergency outage demo flow
 

Demo Flow JSON

Instead of recreating the Studio Flow as shown, you can Import a JSON because copy-and-pasting JSON code is easier than dragging and dropping Twilio Studio widgets. To do this, open Studio > Flows from the Navigation Pane and click on the Plus icon.

Give your new Studio Flow a name. I used EmergencyDemo. Click Next.

An image of a how to name a new Studio flow
 

On the Template window, scroll to the bottom and select Import from JSON:

 

An image of the options that Twilio Studio provides for creating a flow including "Import from JSON"

On the JSON Flow definition window, remove the existing {}, paste in the following JSON, and click Next:

An image showing where to place code when creating a new Studio Flow
{
  "description": "Emergency Outage Demo",
  "states": [
	{
  	"name": "Trigger",
  	"type": "trigger",
  	"transitions": [
    	{
      	"event": "incomingMessage"
    	},
    	{
      	"next": "CheckEmergency",
      	"event": "incomingCall"
    	},
    	{
      	"event": "incomingRequest"
    	}
  	],
  	"properties": {
    	"offset": {
      	"x": 0,
      	"y": 0
    	}
  	}
	},
	{
  	"name": "CheckEmergency",
  	"type": "run-function",
  	"transitions": [
   	 {
      	"next": "EmergencySplit",
      	"event": "success"
    	},
    	{
      	"event": "fail"
    	}
  	],
  	"properties": {
    	"service_sid": "<<your service sid>>.",
    	"environment_sid": "<<your environment sid>>.",
    	"offset": {
      	"x": 150,
      	"y": 170
    	},
    	"function_sid": "<<your function sid>>.",
    	"parameters": [
      	{
        	"value": "<<your call center location>>",
        	"key": "location"
      	},
      	{
        	"value": "read",
        	"key": "operation"
      	}
    	],
    	"url": "https://<<your function>>.twil.io/updateSync"
  	}
	},
	{
  	"name": "EmergencySplit",
  	"type": "split-based-on",
  	"transitions": [
    	{
      	"next": "NormalOperation",
      	"event": "noMatch"
    	},
    	{
      	"next": "EmergencyOperation",
      	"event": "match",
      	"conditions": [
        	{
          	"friendly_name": "If value equal_to true",
          	"arguments": [
            	"{{widgets.CheckEmergency.parsed.Enabled}}"
          	],
          	"type": "equal_to",
          	"value": "true"
        	}
      	]
        }
  	],
  	"properties": {
    	"input": "{{widgets.CheckEmergency.parsed.Enabled}}",
    	"offset": {
      	"x": 150,
      	"y": 400
    	}
  	}
	},
	{
  	"name": "EmergencyOperation",
  	"type": "say-play",
	  "transitions": [
    	{
      	"event": "audioComplete"
    	}
  	],
  	"properties": {
    	"voice": "Polly.Amy",
    	"offset": {
      	"x": 370,
      	"y": 630
    	},
    	"loop": 1,
    	"say": "{{widgets.CheckEmergency.parsed.Message}}",
    	"language": "en-GB"
  	}
	},
	{
  	"name": "NormalOperation",
  	"type": "say-play",
  	"transitions": [
    	{
      	"event": "audioComplete"
    	}
  	],
  	"properties": {
    	"voice": "Polly.Amy",
    	"offset": {
      	"x": -50,
      	"y": 630
    	},
    	"loop": 1,
    	"say": "Normal operation",
    	"language": "en-GB"
  	}
	}
  ],
  "initial_state": "Trigger",
  "flags": {
	"allow_concurrent_calls": true
  }
}

This JSON object is the call flow as pictured above. It runs a Function to check for an emergency. If there is no emergency, it proceeds to normal operation. If there is an emergency, it plays the emergency message.

Now you see our EmergencyDemo flow, and you can see an exception (red circle with exclamation mark) on the CheckEmergency Function block of your flow:

 

An image of the emergency demo flow including an exception (the red exclamation point)  for the "Check Emergency" block

This exception happened because you need to update the block with the Function you created. Simply click on the CheckEmergency block, and configure the Function with the Service = EmergencyOutage, Environment = ui and Function = /readUpdateOutage. Then click Save as shown:

An image showing how to update the "Check Emergency" section of the flow with the readUpdateOutage function.

Double check your location in the Function’s Parameters section, and make any changes to the location necessary:

An image showing how to update the "Check Emergency" section of the flow with the readUpdateOutage function

Then Publish the Flow:

 

An image showing how to publish a Studio Flow

Your Studio Flow is now ready to Assign to a Phone Number.

Assign Flow to Phone Number

In the Navigation Pane, select Phone Numbers > Manage > Active numbers:

Select the Number you wish to use with your EmergencyDemo flow (or optionally buy a new number) and scroll down to the Voice & Fax section. In the A CALL COMES IN section, select Studio Flow, select EmergencyDemo, then click Save:

 

An image showing how to tell the active phone number to route to the studio flow using "A Call Comes In"

Testing the solution

In order to test the solution you can simply call your number you just configured with the Studio Flow. You should hear “Normal Operation” and then the call will hang up. In a real-world example you would then provide a menu of phone options you would like your customer to hear and interact with.

Let’s set an outage:

Using the Navigation Pane open the previously created Sync Map (Outages) and select Edit for the Map Item (Manila):

An image showing how to edit a SyncMap
 

Update the Data field with the following and click Save:

{
  "Begins": "04042021",
  "Message": "This is a test. We should hear this message if there is an outage",
  "Enabled": true
}

An image showing the new code to add to the data portion of the Sync Map

Note: If the Begins date is in the future, the outage will not be enabled.

Call your number again with the Studio Flow. You should hear “This is a test. We should hear this message if there is an outage” and then the call will hang up. In a real-world example you would then configure your intended response such as voicemail, or re-queueing the call to another location.

Conclusion

This solution makes it easy to set an outage and play a message for your callers after a few easy steps. It helps alleviate the frustration of waiting in an unmanned queue by disconnecting the call immediately, or setting expectations on wait times, or even re-directing the queue to give callers the best experience.

Optional Part 2: Creating a Remotely Activated Emergency Outage Message in Twilio

In the event of an emergency, you may not have the time to update the Sync Map to enable the emergency. Having a remote way to update this message saves time in these events. For this option you can use SMS messaging to enable or disable the emergency outage remotely. A correctly formatted message from a recognised number is all you need to perform this task. 

To enable an Emergency you will send a SMS message in the following format:

<<Location>>: Message

For example:

Manila: Due to unseen circumstances, our call center is unavailable. Please try again later.

If the number is on the SMS approved list, you will receive a message asking when you want the outage to start to which you can reply with a date in the format of DDMMYYY (07112021 for 7 November 2021). This also allows us to schedule a message in the future for such things as public holidays.

If the date entered is not valid, the user is prompted again and reminded about the format.

If the date is in the past, they will receive an SMS like ***Warning: Start Date is in the past*** Outage message for <<Location>>: <<Message>> starting on <<Start Date>>

If the date is in the future, they will receive an SMS like ***Warning: Start Date is in the future, no outage will be enabled until that time*** Outage message for <<Location>>: <<Message>> starting on <<Start Date>>.

To remove the outage we simply message <<Location>>: remove (e.g. Manila: remove) to disable the message.

 

Flow from an inbound call to the Twilio Function to Twilio Sync either to enable or disable outage

Function splitLocationMessage

In this section you will outline the Function needed, check the incoming SMS, and break it down into Location and Message. In the Navigation Pane expand Functions and select Services.

Select your EmergencyOutage Service:

 

An image of the screen for selecting the existing EmergencyOutage Service previously created

Click on Add + > Add Function and provide /splitLocationMessage as a Function Path:

An image of adding a function path called splitLocationMessage

Leave the Function as Protected, and paste in the following code. Then click Save:

exports.handler = function(context, event, callback) {
  console.log(event.Body);
  let message = event.Body.split(":"); //Split the message by :
  let location = message[0]; // Set the Location
  let outageMessage = message[1]; // Set the Message
  let response = {"location": location, "outageMessage": outageMessage}
  return callback(null, response); //Return the Location and Message back to Studio
 
};

This code looks at the SMS message and splits it into 2 parts (separated by the colon ":"). The first part is then assigned to location and the second part is assigned to the message. Both parts return to the flow.

Function checkDateFormat

Like the previous task, you will create a new Function in your existing Service. Click Add + and use /checkDateFormat as the path:

 

An image of adding a function path called checkDateFormat

Leave the Function as Protected, and paste in the following code. Then, click Save:

var moment = require('moment'); // require
exports.handler = function(context, event, callback) {
console.log(event)
 let day = event.startDate.slice(0,2); //Take the date, and get the day value
 let month = event.startDate.slice(2,4); //Take the date, and get the month value
 let year = event.startDate.slice(4,8); //Take the date, and get the year value
 let startDate = moment(year+"-"+month+"-"+day); //Format the date for the Moment module
 console.log(startDate) //Log the date for debugging purposes
 let response = {"past": false}
 if(startDate.isBefore()){ //Check if the date is in the past
 	response.past = true;
 }
 if(startDate.isValid() ){ //Check if we have a valid date
 	console.log("Valid Date");
 	response.valid = true;
 	response.date = startDate.format('LL') //Return the date in words, e.g. 7 November 2021
 }else{
 	console.log("Invalid Date");
 	response.valid = false;
 }
 console.log(response); //Return date validity and if in the past
 callback(null,response);
};

This code checks the date to make sure the date is a valid format. It also checks if the date is today, in the past, or in the future, then the details pass back to the flow.

Click Deploy All:

 

An image showing how to click Deploy All after adding a path to the Function

Your Functions are ready for the Studio Flow.

Outage Flow

The outage flow pictured below will enable you to trigger and remove an emergency outage with a text message.

An image of the outage flow that will allow us to enable or disable an outage message via SMS

Outage Flow JSON

Instead of recreating the Studio Flow as shown, you can Import a JSON.

To do this open Studio > Flows from the Navigation Pane and click on the Plus icon. Give your new Studio Flow a name. I used UpdateOutage. Then click Next:

A screenshot of naming a new flow. In this case the name is UpdateOutage

On the Template window, scroll to the bottom, select Import from JSON, and paste in the following code:

{
  "description": "UpdateOutage",
  "states": [
    {
      "name": "Trigger",
      "type": "trigger",
      "transitions": [
        {
          "next": "AllowedNumbers",
          "event": "incomingMessage"
        },
        {
          "event": "incomingCall"
        },
        {
          "event": "incomingRequest"
        }
      ],
      "properties": {
        "offset": {
          "x": 0,
          "y": 0
        }
      }
    },
    {
      "name": "AllowedNumbers",
      "type": "split-based-on",
      "transitions": [
        {
          "next": "UnauthorisedNumber",
          "event": "noMatch"
        },
        {
          "next": "GetLocationMessage",
          "event": "match",
          "conditions": [
            {
              "friendly_name": "If value equal_to +XXXXXXXXXX",
              "arguments": [
                "{{trigger.message.From}}"
              ],
              "type": "equal_to",
              "value": "+XXXXXXXXXX"
            }
          ]
        }
      ],
      "properties": {
        "input": "{{trigger.message.From}}",
        "offset": {
          "x": -40,
          "y": 210
        }
      }
    },
    {
      "name": "UnauthorisedNumber",
      "type": "send-message",
      "transitions": [
        {
          "event": "sent"
        },
        {
          "event": "failed"
        }
      ],
      "properties": {
        "offset": {
          "x": -220,
          "y": 440
        },
        "service": "{{trigger.message.InstanceSid}}",
        "channel": "{{trigger.message.ChannelSid}}",
        "from": "{{flow.channel.address}}",
        "to": "{{contact.channel.address}}",
        "body": "Please message with the designated number"
      }
    },
    {
      "name": "GetStartDate",
      "type": "send-and-wait-for-reply",
      "transitions": [
        {
          "next": "CheckDate",
          "event": "incomingMessage"
        },
        {
          "event": "timeout"
        },
        {
          "event": "deliveryFailure"
        }
      ],
      "properties": {
        "offset": {
          "x": -10,
          "y": 930
        },
        "service": "{{trigger.message.InstanceSid}}",
        "channel": "{{trigger.message.ChannelSid}}",
        "from": "{{flow.channel.address}}",
        "body": "When would you like the outage to start? Format DDMMYYYY.",
        "timeout": "360"
      }
    },
    {
      "name": "SendConfirmation",
      "type": "send-message",
      "transitions": [
        {
          "event": "sent"
        },
        {
          "event": "failed"
        }
      ],
      "properties": {
        "offset": {
          "x": -70,
          "y": 2140
        },
        "service": "{{trigger.message.InstanceSid}}",
        "channel": "{{trigger.message.ChannelSid}}",
        "from": "{{flow.channel.address}}",
        "to": "{{contact.channel.address}}",
        "body": "***Warning: Start Date is in the future, no outage will be enabled until that time***  Outage message for {{widgets.GetLocationMessage.parsed.location}}: \"{{widgets.GetLocationMessage.parsed.outageMessage}}\" starting on {{widgets.CheckDate.parsed.date}}."
      }
    },
    {
      "name": "CheckDate",
      "type": "run-function",
      "transitions": [
        {
          "next": "ValidDate",
          "event": "success"
        },
        {
          "event": "fail"
        }
      ],
      "properties": {
        "service_sid": "<<your service sid>>",
        "environment_sid": "<<your environment sid>>",
        "offset": {
          "x": -10,
          "y": 1160
        },
        "function_sid": "<<your function sid>>",
        "parameters": [
          {
            "value": "{{widgets.GetStartDate.inbound.Body}}",
            "key": "startDate"
          }
        ],
        "url": "https://<<your function>>.twil.io/checkDateFormat"
      }
    },
    {
      "name": "ValidDate",
      "type": "split-based-on",
      "transitions": [
        {
          "next": "WrongFormat",
          "event": "noMatch"
        },
        {
          "next": "EnableMessageSync",
          "event": "match",
          "conditions": [
            {
              "friendly_name": "If value equal_to true",
              "arguments": [
                "{{widgets.CheckDate.parsed.valid}}"
              ],
              "type": "equal_to",
              "value": "true"
            }
          ]
        }
      ],
      "properties": {
        "input": "{{widgets.CheckDate.parsed.valid}}",
        "offset": {
          "x": -10,
          "y": 1400
        }
      }
    },
    {
      "name": "WrongFormat",
      "type": "send-message",
      "transitions": [
        {
          "next": "GetStartDate",
          "event": "sent"
        },
        {
          "event": "failed"
        }
      ],
      "properties": {
        "offset": {
          "x": -230,
          "y": 1640
        },
        "service": "{{trigger.message.InstanceSid}}",
        "channel": "{{trigger.message.ChannelSid}}",
        "from": "{{flow.channel.address}}",
        "to": "{{contact.channel.address}}",
        "body": "That is an incorrect format"
      }
    },
    {
      "name": "CheckPast",
      "type": "split-based-on",
      "transitions": [
        {
          "next": "SendConfirmation",
          "event": "noMatch"
        },
        {
          "next": "SendPastConfirmation",
          "event": "match",
          "conditions": [
            {
              "friendly_name": "If value equal_to true",
              "arguments": [
                "{{widgets.CheckDate.parsed.past}}"
              ],
              "type": "equal_to",
              "value": "true"
            }
          ]
        }
      ],
      "properties": {
        "input": "{{widgets.CheckDate.parsed.past}}",
        "offset": {
          "x": 260,
          "y": 1880
        }
      }
    },
    {
      "name": "SendPastConfirmation",
      "type": "send-message",
      "transitions": [
        {
          "event": "sent"
        },
        {
          "event": "failed"
        }
      ],
      "properties": {
        "offset": {
          "x": 490,
          "y": 2150
        },
        "service": "{{trigger.message.InstanceSid}}",
        "channel": "{{trigger.message.ChannelSid}}",
        "from": "{{flow.channel.address}}",
        "to": "{{contact.channel.address}}",
        "body": "**Warning: Start Date is today or in the past**\nOutage message for {{widgets.GetLocationMessage.parsed.location}}: \"{{widgets.GetLocationMessage.parsed.outageMessage}}\" will be enabled immediately."
      }
    },
    {
      "name": "GetLocationMessage",
      "type": "run-function",
      "transitions": [
        {
          "next": "CheckRemove",
          "event": "success"
        },
        {
          "event": "fail"
        }
      ],
      "properties": {
        "service_sid": "<<your service sid>>",
        "environment_sid": "<<your environment sid>>",
        "offset": {
          "x": 200,
          "y": 440
        },
        "function_sid": "<<your function sid>>",
        "parameters": [
          {
            "value": "{{trigger.message.Body}}",
            "key": "Body"
          }
        ],
        "url": "https://<<your function>>.twil.io/splitLocationMessage"
      }
    },
    {
      "name": "CheckRemove",
      "type": "split-based-on",
      "transitions": [
        {
          "next": "GetStartDate",
          "event": "noMatch"
        },
        {
          "next": "DisableMessageSync",
          "event": "match",
          "conditions": [
            {
              "friendly_name": "If value equal_to remove",
              "arguments": [
                "{{widgets.GetLocationMessage.parsed.outageMessage}}"
              ],
              "type": "equal_to",
              "value": "remove"
            }
          ]
        }
      ],
      "properties": {
        "input": "{{widgets.GetLocationMessage.parsed.outageMessage}}",
        "offset": {
          "x": 240,
          "y": 680
        }
      }
    },
    {
      "name": "EnableMessageSync",
      "type": "run-function",
      "transitions": [
        {
          "next": "CheckPast",
          "event": "success"
        },
        {
          "next": "CheckPast",
          "event": "fail"
        }
      ],
      "properties": {
        "service_sid": "<<your service sid>>",
        "environment_sid": "<<your environment sid>>",
        "offset": {
          "x": 170,
          "y": 1610
        },
        "function_sid": "<<your function sid>>",
        "parameters": [
          {
            "value": "{{widgets.GetLocationMessage.parsed.outageMessage}}",
            "key": "outageMessage"
          },
          {
            "value": "{{widgets.GetLocationMessage.parsed.location}}",
            "key": "location"
          },
          {
            "value": "{{widgets.CheckDate.parsed.date}}",
            "key": "date"
          },
          {
            "value": "add",
            "key": "operation"
          },
          {
            "value": "{{contact.channel.address}}",
            "key": "from"
          }
        ],
        "url": "https://<<your function>>.twil.io/readUpdateOutage"
      }
    },
    {
      "name": "DisableMessageSync",
      "type": "run-function",
      "transitions": [
        {
          "next": "send_message_1",
          "event": "success"
        },
        {
          "event": "fail"
        }
      ],
      "properties": {
        "service_sid": "<<your service sid>>",
        "environment_sid": "<<your environment sid>>",
        "offset": {
          "x": 550,
          "y": 920
        },
        "function_sid": "<<your function sid>>",
        "parameters": [
          {
            "value": "{{widgets.GetLocationMessage.parsed.outageMessage}}",
            "key": "outageMessage"
          },
          {
            "value": "{{widgets.GetLocationMessage.parsed.location}}",
            "key": "location"
          },
          {
            "value": "{{widgets.CheckDate.parsed.date}}",
            "key": "date"
          },
          {
            "value": "remove",
            "key": "operation"
          },
          {
            "value": "{{contact.channel.address}}",
            "key": "from"
          }
        ],
        "url": "https://<<your function>>.twil.io/updateSync"
      }
    },
    {
      "name": "send_message_1",
      "type": "send-message",
      "transitions": [
        {
          "event": "sent"
        },
        {
          "event": "failed"
        }
      ],
      "properties": {
        "offset": {
          "x": 540,
          "y": 1150
        },
        "service": "{{trigger.message.InstanceSid}}",
        "channel": "{{trigger.message.ChannelSid}}",
        "from": "{{flow.channel.address}}",
        "to": "{{contact.channel.address}}",
        "body": "Outage message for {{widgets.GetLocationMessage.parsed.location}} cancelled."
      }
    }
  ],
  "initial_state": "Trigger",
  "flags": {
    "allow_concurrent_calls": true
  }
}

This JSON object is the flow pictured above. It checks the SMS messages for both the location and message. If the message includes “remove,” then the function will remove the outage details. If the message indicates an outage, then it will ask for the date. It’ll check the date to see if it’s in the past, present, or future, and send back a message to the admin based on the results.

Outage Flow Exceptions

Your UpdateOutage flow is created, and there are  4 exceptions (Red circle with exclamation mark) on the following blocks of the flow that need to be resolved.

Get Location Message

An image showing an exception (red exclamation point) on the GetLocationMessage block

Click on this block and set the SERVICE to EmergencyOutage, the ENVIRONMENT to ui and the FUNCTION to /splitLocationMessage, then click Save:

An image showing how to set the parameters for the GetLocationMessage
Disable Message Sync

An image showing an exception (red exclamation point) on the DisableMessageSync

Click on this block and set the SERVICE to EmergencyOutage, the ENVIRONMENT to ui and the FUNCTION to /readUpdateOutage, then click Save:

An image showing how to set the parameters for the DisableMessageSync
Check Date

An image showing an exception (red exclamation point) on the CheckDate

Click on this block and set the SERVICE to EmergencyOutage, the ENVIRONMENT to ui and the FUNCTION to /checkDateFormat, then click Save:

An image showing how to set the parameters for the CheckDate
Enable Message Sync

An image showing an exception (red exclamation point) on the EnableMessageSync block

Click on this block and the SERVICE to EmergencyOutage, the ENVIRONMENT to ui and the FUNCTION to /readUpdateOutage, then click Save:

An image showing how to set the parameters for the Enable MessageSync

Allowed Numbers

You also have to update the AllowedNumbers block to reflect the mobile number for your admins. Each admin’s phone number should have its own Condition, and point to the GetLocationMessage as shown. Once changed click Save:

An image showing how to set the allowed numbers for this flow

Then Publish the Flow:

 

An image of how to publish the Studio Flow

Assign Flow to Phone Number

In the Navigation Pane, select Phone Numbers > Manage > Active numbers:

Select the Number you wish to use with your UpdateOutage flow (or optionally buy a new number) and scroll down to the Messaging section. In the A MESSAGE COMES IN section, select Studio Flow, select UpdateOutage, and then click Save:

 

An image showing how to tell the active phone number to route to the studio flow using "A Message Comes In"

Testing the solution

In the first part of this blog post, you called the number and heard “Normal Operation”. To enable the outage, you can send an SMS containing “Manila: This is a test outage message” (replacing “Manila” with the location you used) to your configured number in the previous step. You will get a follow up message asking when to start the message. Enter today, or a date in the past in the format of DDMMYYYY (e.g. 07112021 for 7 November 2021). Then call the Demo Studio Flow number you configured in the last blog. You should hear the “This is a test outage message” prompt.

To remove the outage, send an SMS containingManila: remove” (replacing “Manila” with the location you used) to the Update Outage phone number, then call the Emergency Demo phone number again. You should hear the “Normal Operation” message again.

Lastly you can test entering a future date to start our outage by texting “Manila: This is a second test outage message”. In the follow-up, enter a future date. Then call the Demo Studio Flow number you configured in the last blog. You should hear the “Normal Operation” message again. However, if you call on the date you specified in the outage flow, you will hear “This is a test outage message.

An image of the SMS conversation initiated by the allowed number

Conclusion

With this solution in place, you now have the ability to quickly activate or deactivate an emergency outage notification no matter where you are using a simple SMS message. Your customers will not need to wait in frustrating unmanned queues and you can operate with maximum efficiency in rapidly changing situations.  

Robert is a Senior Solutions Architect at Twilio working in the Professional Services team in Singapore. You can reach him at rhockley [at] twilio.com