Please ensure Javascript is enabled for purposes of website accessibility
Twilio Flex Integration Guide
  • 30 Nov 2023
  • 38 Minutes to read
  • Dark
    Light
  • PDF

Twilio Flex Integration Guide

  • Dark
    Light
  • PDF

Article Summary

This guide provides comprehensive instructions for integrating Mindful Callback with Twilio Flex. Here you can learn how to configure Mindful Callback, Feedback, Scheduler, and Datastore to integrate with Twilio Flex. Each step in the integration process will be demonstrated through examples that can be used as templates for your specific integration.

Overview

  • Step 1: Configure your Mindful account
    • Configure Call Targets in Mindful Callback
    • Configure a Scheduler Widget for each Call Target used in the integration
    • (Optional) Configure a Data Set Template in Mindful Datastore
  • Step 2: Configure Twilio Flex
    • Enable Enhanced Programmable SIP Features
    • Create an Access Control List (ACL) for Mindful Callback
    • Create an Elastic SIP Trunk for Mindful Callback
    • Create a new SIP Domain for Mindful Callback
    • Create a new TWIML Bin for transfers to Mindful
    • Configure Twilio functions
      • Create a new service
      • Create the Convert EWT Phrase Function (convertEWT)
      • Create a Retrieve Widget Status Function (getWidgetStatus)
      • (Optional) Create a Mindful Datastore Post Function (datastorePost)
      • (Optional) Create a Mindful Datastore Get function (datastoreGet)
      • Create a Normalize ANI function (normalizeANI) if you are using Mindful Datastore
    • Create Twilio Studio Flows (inbound and callback return)
  • Step 3: (Optional) Configure Second Chance Callback
    • Create two new Twilio functions
    • Update the Inbound Flow

Additional integrations

  • Mindful Feedback: Trigger post-call surveys from your Studio Flows, including voice IVR surveys, email surveys, web chat integration, and more! Visit the Twilio Flex and Feedback section of the Help Center to learn more.

Components and call flows

Before looking at configuration, you can review the content below to review key terms and acronyms used throughout this guide.

Definitions and acronyms

 
TermDescription
ANI (or CLI)Automatic Number Identification is the number of the caller. Also known as Calling Line Identification in some markets.
APIAn Application Programming Interface is a set of functions that allows applications to request service from other applications.
DIDDirect Inward Dialing phone number 
DNISThe Dialed Number Identification Service is the number of the callee (typically the number that is being dialed) in a voice call.
EWTEstimated Wait Time, as calculated by Twilio Flex for a Task Queue.
Mindful CallbackA part of the Mindful platform that provides the capability to request and receive callbacks.
DatastoreThis service adds the capability to store and retrieve custom data when registering a callback in Mindful via HTTPS POST and GET calls.
SchedulerA feature of Mindful Callback that allows callbacks to be requested via API from third party applications/platforms.
PSTNPSTN is the traditional circuit-switched telephone network that comprises all the world's telephone networks.
RTP/SRTPReal-time Transport Protocol is a network protocol used to deliver audio and video over IP networks. SRTP is the secure (encrypted) version of RTP.
SIDA String Identifier is a unique 34-character key used to identify unique resources in Twilio.
SIPSession Initiation Protocol is a signaling protocol used in IP communications.
TFNToll-free number
TLSTransport Layer Security is an encryption protocol used for secure SIP signaling between two endpoints.
Twilio StudioA workflow application builder used to develop flows for voice and messages
TWIMLTwilio Markup Language is an XML-based language that instructs Twilio how to handle certain events and perform certain actions related to calls and messages.

Inbound call flow

inbound call flow diagram

  1. A customer calls into a Twilio contact center.
  2. The customer DID is matched to an Inbound Studio flow.
  3. After checking the Estimated Wait Time and the Mindful Callback status, the Inbound flow may optionally post data to the Mindful Datastore using a function.
  4. The call is transferred to the SIP phone number provisioned of a Call Target in Mindful using a TWIML redirect widget and TWIML bin.
    1. If one of the following conditions occur, the call will be sent to the agent queue at normal priority instead of being transferred to Mindful:
      • The Mindful Callback availability check fails.
      • The EWT is below the configured threshold.
      • There is an error in the flow.
      • The caller declines the callback offer and chooses to wait in queue for an agent.
  5. Twilio sends a SIP Invite to Mindful Callback and the caller is offered a callback.

This diagram shows the callback requested via an inbound call into Mindful Callback. However, callbacks may also be requested using the Mindful Scheduler API, with associated user data sent to the Mindful Datastore via the Datastore API. Regardless of which method is used, the callback flow will be the same.


Choose-hold call flow

choose hold call flow diagram

  1. The caller declines the callback offer. Mindful issues a SIP REFER to transfer the call back into the Twilio SIP Domain.
  2. As the SIP leg to Mindful Callback was initiated by a TWIML Redirect widget, the REFER is accepted by Twilio. Mindful is removed from the call path.
  3. The Inbound Studio flow resumes after the TWIML Redirect widget.
  4. The call is sent to an agent queue at normal inbound call priority.

Callback call flow

callback call flow diagram

  • Mindful calls the customer callback number via an INVITE to an Elastic SIP Trunk in Twilio.
  • Twilio calls the customer over the local carrier network. The customer answers and presses DTMF 1 to confirm they wish to speak to an agent.
  • Mindful Callback sends an INVITE to a Twilio SIP Domain configured for callbacks.
  • The incoming call to the Twilio SIP Domain is matched to the Return Call Studio flow.
  • The Return Call Studio flow may optionally retrieve the inbound user data from the Mindful Datastore using a function, with the caller’s ANI passed in as the search key.
  • The call is queued for agents at a higher priority than inbound calls.
NOTE
This shows a typical customer-first call flow. Mindful Callback also supports an agent-first call flow in which the agent leg is initiated first. The Twilio configuration and Studio flows are the same regardless of which method is used.

Step 1: Configure your Mindful account

Before your ACD can send inbound calls to Mindful, there are a few items that must be configured on the Mindful side:

  • At least one Call Target to register and dial callbacks
  • One Scheduler Widget for each Call Target used in the integration
  • A Data Set Template in Mindful Datastore (if you intend to use Datastore to store custom user data and context)

Call Target configuration

There are a few settings in Callback that must be adjusted to integrate with your ACD.

Registration

Quick access: Callback > Call Targets > Your Call Target > General tab > Registration

  • Offer ASAP Callback: Select this checkbox to offer callbacks to be returned as soon as possible.
  • Offer Choose Hold: Select this to offer callers the option to wait on hold rather than accepting the offer of a callback.
  • Offer Scheduled Callback: Select one or both of these checkboxes (Voice and/or Widget/API) if you wish to offer callback scheduling for specific dates and times.

Contact Center

Quick access: Voice > Call Targets > Your Call Target > General tab > Contact Center

  • Callback Telephony Type: Select SIP.
  • Callback Number: This will be configured in a later step.
  • Choose Hold Telephony Type: Select SIP.
  • Choose Hold Number: This will be configured in a later step.

Callback Strategy

Quick access: Voice > Call Targets > Your Call Target > General tab > Callback Strategy

Most of the Callback Strategy settings are not relevant to the integration, and they can be set however you would like. However, there is one notable exception when using the Customer First Callback Strategy.

When using Customer First, enable Wait for live Agent. This will prompt agents to press a digit to accept a callback, which provides an Agent Answer event to Mindful. The Agent Answer events assist in calculating an accurate Estimated Callback Time (ECBT).

screenshot of the wait for live agent setting

Phone Numbers

Quick access: Configuration > Phone Numbers

On the Phone Numbers page, provision as many SIP numbers as needed and assign a number to each Call Target in your Organization. This is the number to use when configuring the SIP endpoint to which to send inbound calls for callback treatment.

Scheduler Widget Configuration

Each Call Target used in the integration will require a Scheduler Widget. These Widgets will provide API endpoints that will allow your ACD to check the status of a Call Target before forwarding an inbound call to Mindful. Important points for the Widgets used in this integration are listed below:

  • Template: Select any Scheduler Template. Templates will not be used since this Widget will only be accessed via API, but Mindful Callback requires a Template to be assigned.
  • Call Target: Select the appropriate Call Target. The ACD will check the status of this Call Target (via the Widget) to ensure it is ready to register callbacks before transferring inbound calls to Mindful.

For complete instructions on creating Widgets, see Getting Started with Scheduler Widgets.

Datastore configuration

Mindful Datastore allows you to store user data to maintain call context at critical points in an interaction. If you intend to use Datastore, you will need to perform the next few steps within the Datastore user interface.

A Data Set Template contains a collection of Data Keys that allows the Mindful Datastore to store customer data during the callback request process. This collection includes:

  • The set's name and description
  • How long you want to retain collected data submitted with this Data Set
  • What information you want the set to collect (through configuration of Data Keys)
  • The API token that is used to associate data submitted via POST requests and return information via GET requests

The Mindful Solution Delivery team can assist with setting up a Data Set Template, as well as a unique authentication token. To create one on your own, use the steps below.

Quick access: Datastore > Data Set Templates

  1. On the Data Set Templates page, click Add Data Set Template. This takes you to the New Data Set Template page.

screenshot of the data set templates page

screenshot of the new data set template page

  1. Name: Enter a name that will be recognizable to others in your organization.
  2. Description: Enter a description for the benefit of other Administrators.
  3. Data Retention Period (Hours): Manually enter or use the +/- buttons to customize your Data Retention Period. You can retain data for 1 hour, or for up to 48 hours.
  4. API Token: The system automatically generates your API Token.
IMPORTANT
If your API Token is already plugged into your routing logic, regenerating the token here will break that link. To re-establish the link, update your host with the new API Token. Contact Mindful Support for assistance.
  1. Template Data Keys: You can select from existing Data Keys in your system or add new keys. The selected keys will filter out any submitted data that does not correspond to one of the configured keys, and will only retain submitted data that matches the configured keys.

Add a Data Key

Click Add Template Data Key. This opens the New Template Data Key modal window.

  1. Click in the Manually Enter Data Keys field. 
    1. If your Mindful Datastore instance contains existing Data Keys, select one from the dropdown list that appears.
    2. If not, type the name of a new Data Key here.
  2. When finished, click Add.

Your key now appears in the Template Data Keys list.

EXAMPLE
In our example integration, we set up the following Data Keys for callbacks:
example data keys
  • FirstName
  • LastName
  • AccNum
  • CallId

You can configure Data Keys in the same way for any user data that you need to maintain in your environment.


Step 2: Configure your Twilio Flex account

For the SIP call flows to and from Mindful Callback to function correctly, two items are needed:

  • A terminating Elastic SIP trunk in Twilio
  • A new SIP Domain for calls from Mindful to the callback Studio flow

The Mindful SIP URL and IP addresses in the sample configuration below may be different for each Twilio Flex integration. Contact the Mindful Solution Delivery Team to confirm the SIP URL and IP addresses relevant to your integration.

Enable Enhanced Programmable SIP Features

Quick access: Voice > Settings > General

For the SIP REFER with the TWIML Redirect functionality to work, the Enhanced Programmable SIP Features must be enabled. This option is Disabled by default. Switch the toggle switch to Enabled before moving on.

screenshot of the enhanced programmable sip features setting

Enabling this feature can change the behavior of the <Dial> TwiML verb across the Twilio account. Refer to the Twilio documentation on this feature before enabling it on your account.

Create an Access Control List (ACL) for Mindful Callback

Quick access: Voice > Manage > IP Access Control Lists

To allow Mindful to call customers via the Elastic SIP Trunk and call into the Flex contact center via the SIP Domain, you will need an ACL containing the Mindful Callback SIP IP addresses. These IPs may vary depending on the region of your Mindful organization. The correct SIP IPs will be provided by the Mindful Solution Delivery team.

Below is an example of a new ACL showing some example Mindful Callback SIP IP addresses:

screenshot of an example access control list

Create an Elastic SIP Trunk for Mindful Callback

Quick access: Elastic SIP Trunking > Manage > Trunks

Mindful Callback uses an Elastic SIP Trunk configured in your Twilio environment to dial customers. In this guide, we will create a new Elastic SIP Trunk for this purpose, but you can use an existing Elastic SIP Trunk if you'd like. If you choose to use an existing trunk, simply add the newly created Mindful ACL to the Authentication section of the Elastic SIP Trunk configuration.

Follow the steps below to create a new trunk.

  1. Click Create new SIP Trunk on the Elastic SIP Trunks page.
  2. On the General page, give the trunk a name:

screenshot of sip trunk configuration


  1. If desired, you can set the Secure Trunking option to Enabled to allow secure SIP and SRTP for this trunk. If Secure Trunking is enabled, you must advise the Mindful Solution Delivery team that the SIP Termination URI for callbacks should use SIP TLS on port 5061.


screenshot of the secure trunking setting


  1. Click Termination in the left navigation menu.
  2. On the Termination page, give the Termination SIP URI a unique name.
    • This URI is unique across Twilio, so it cannot be the same as a SIP URI name used in any other Twilio account. The example below shows a URI name of mindful-callback, so this URI cannot be used in any other Twilio accounts.


screenshot of the termination U.R.I. field


  1. In the Authentication section, select the ACL configured in a previous step.


screenshot of the authentication section


  1. Click Save to add this trunk before moving on.

Your trunk configuration is complete! Nothing is required on the Origination page, and you do not need to assign any numbers to this trunk.

Create a new SIP Domain for Mindful Callback

Quick access: Voice > Manage > SIP domains

You will need a new SIP domain for return calls from Mindful to your contact center. This domain will be used for choose-hold calls (using SIP REFER) and for the agent leg of callbacks.

To create a new SIP Domain:

  1. Click the plus (+) icon above the list of existing SIP domains.
  2. Give the SIP domain a name.
  3. Enter a SIP URI
    • This can be the same URI name as the previously created trunk (no conflict will occur since the extension is sip.twilio.com instead of pstn.twilio.com).


screenshot of sip domain configuration


  1. In the Authentication section, select the ACL you configured previously:


screenshot of sip domain configuration


  1. In the Call Control Configuration section, select the Studio Flow that will be used to queue callbacks to agents when a call arrives. If the Return Call Studio Flow has not yet been created, select any flow as a placeholder, then set this field to the Callback Studio Flow once it has been published.


screenshot of sip domain configuration


  1. (Optional) If desired, you can enable Secure Media to ensure TLS is used for signaling and SRTP for audio. If this is enabled, advise the Mindful Solution Delivery team that the choose-hold and callback URIs should use SIP TLS on port 5061.


screenshot of the secure media setting


  1. Click Save to add the new domain.

Create a new TWIML Bin for transfers to Mindful

Quick access: TwiML Bins > My TwiML bins

You will need a new TwiML bin to allow inbound calls to be transferred to Mindful, then transferred back to Twilio using SIP REFER (if the caller declines the offer). In a later step, we will configure a TwiML Redirect step in the Inbound Studio flow to target this new bin.

To create a new TwiML bin:

  1. Click the plus (+) icon:

twiml bin

  1. Note down the URL of this TwiML bin. You will need it later for the TwiML Redirect widget in the Inbound flow.
  2. Enter a friendly name.
  3. Add the TwiML used to transfer the call to Mindful Callback. See below for an example:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Dial referUrl="https://webhooks.twilio.com/v1/Accounts/ACd996d03de9axxxxxxxxxxxxxxxx/Flows/FW5334d11bfc9401bxxxxxxxxxxxxxxxx?FlowEvent=return">
    <Sip>sips:{{mindful_number}}@sip-callback.mindful.cx:5061?X-inboundcallid={{callid}}&amp;X-accnum={{accnum}}&amp;X-Mindful-Routing-Token={{routingtoken}}</Sip>
  </Dial>
</Response>

Two lines in the example are especially important:

  • <Dial referUrl=URL>: This contains the URL of the Inbound Studio Flow (the same used to invoke this TwiML bin via TwiML Redirect widget), with FlowEvent=return. This instructs Twilio to return the call to this step in the flow when the call is returned via SIP REFER.
  • <Sip>: This contains the SIP URI used to transfer the call to Mindful Callback.

The <Sip> element contains a SIP address constructed of several parts:

  • {{mindful_number}} is a variable value passed in from the TwiML Redirect widget in the Inbound flow. This is used to send calla to the appropriate Mindful Call Target.
  • @sip-callback.mindful.cx:5061 is the SIP URI host for Mindful Callback. This may differ in your environment, and the SIP URI provided by the Mindful team team should be used here.
    • Note that 5061 is the Mindful Callback SIP TLS port. For UDP, this should be 5060.
  • X-inboundcallid={{callid}}&amp;X-accnum={{accnum}}: These are two example custom SIP headers (X-inboundcallid and X-accnum) with values passed in as variables from the TwiML Redirect widget. Using custom SIP headers is optional. If you use custom SIP headers, there are a few considerations to keep in mind:
    • The SIP header must begin with the X- prefix. If this prefix is missing, Twilio will ignore the custom headers on the callback.
    • When using more than one SIP header, each subsequent header and value must be separated in this line with an &amp; as shown in the example above.
  • X-Mindful-Routing-Token={{routingtoken}}: This takes the value of the routingtoken parameter assigned in the TwiML Redirect step of the Inbound Flow (which will be configured later), and assigns it to a SIP header required for INVITEs to Mindful Callback.

Click Save to add the new TwiML Bin to the My TwiML Bins collection.


Configure Twilio functions

This guide will detail one Twilio service and six functions (three required and three optional). The service will contain the Twilio Functions to be used for the Mindful Integration. The functions are described below:

  • getEWT queries the Twilio Task Router API endpoint to retrieve a suitable real-time statistic to use as the EWT.
  • convertEWT converts the EWT (in seconds) into a phrase that can be spoken back to the caller.
  • getWidgetStatus sends a Retrieve Widget Status API request (HTTPS GET) to a Mindful Scheduler widget. The status returned by the API indicates whether the Call Target is ready to register callbacks or not.
  • (Optional) Datastore Post is invoked by the Inbound flow to send user data to the Mindful Datastore (HTTPS POST).
  • (Optional) Datastore Get is invoked by the Return Call (callback) Studio flow to retrieve the user data associated with the callback (HTTPS GET).
  • (Optional) normalizeANI strips away SIP URI components to format a valid phone number for Datastore API requests.

Create a new service

Quick access: Functions and assets > Services

  1. To create a new service, click Create Service on the Services page.
  2. Enter a name for the new service.


screenshot of the service name field


  1. Click Next and the new service will open. The Functions can now be created in this service.

Create the Get Wait Time Function (getEWT)

The Inbound Studio Flow will check the Estimated Wait Time (in Twilio Flex) to ensure that calls are only sent to Mindful if a defined threshold is met. This also allows the Flow to play an EWT phrase to customers. You will need a Twilio Function to query the Flex API to check the EWT for a specified queue.

IMPORTANT
Twilio recommends caching when using the Task Router API endpoint to ensure that the endpoint can support your scaling needs. The example getEWT Function in this guide does not use any kind of caching, so it may not be suitable for integrations with high call volumes. See the Appendix section of this guide for an example EWT Function with caching.


To create the new function (on the Functions page), click Add above the list of functions. A new function will appear, including sample code. Following is an example of a getEWT function:

/* Retrieves real-time stats for Task Queue
 *  
 * Parameters passing in are:
 * - Workspace_SID - the SID of the Workspace containing the Task Queue
 * - Task_Queue_SID - the SID of the Task Queue to query for real-time statistics
 *
 * Please consider leveraging caching when utilizing the Task Router endpoint to 
 * ensure that the endpoint can support your scaling needs.    
 */

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

  const client = context.getTwilioClient();

  client.taskrouter.workspaces(event.Workspace_SID)
    .taskQueues(event.Task_Queue_SID)
    .realTimeStatistics()
    .fetch()
    .then(task_queue_real_time_statistics => {
      console.log(task_queue_real_time_statistics);
      return callback(null, task_queue_real_time_statistics);
    })
    .catch((error) => {
      console.error("Error: " + error);
    });

};

After configuring the Function, save it before moving on.

This function takes the SIDs of the workspace and task queue as input parameters (passed in from the inbound flow) and returns all real-time statistics available for the specified task queue. In the Inbound flow section of this guide, the longestRelativeTaskAgeInQueue parameter will be parsed from the result and used as the wait-time value. See the Twilio Task Router REST API (Task Queue Statistics) documentation for all available statistics.

Create the Convert EWT Phrase Function (convertEWT)

To help customers make informed decisions when offered a callback, we recommend playing the wait time to customers in the Inbound Studio Flow. The wait time returned by the getEWT function will be quoted in seconds, so you will need a second function to convert the getEWT result into a phrase that can be spoken customers.

Following is an example convertEWT functon:

// Converts EWT value (in seconds) into a usable phrase.

exports.handler = function(context, event, callback) {
    
    var oldestCallWaiting = event.EWT;
    var waitTime = "";

    console.log(oldestCallWaiting);

// catch any low or empty EWT values and announce a minimum time

    if(oldestCallWaiting<120) {

        waitTime = "Current wait time is less than 2 minutes."

// catch any EWT values over the maximum desired amount to announce

    } else if(oldestCallWaiting>7200) {

        waitTime = "Current wait time may be more than 2 hours."

    } else {

        var hours = Math.floor(oldestCallWaiting/3600);
        var minutes = Math.floor((oldestCallWaiting-(hours * 3600))/60);
        var seconds = oldestCallWaiting - (hours*3600) - (minutes*60);
        var hoursPhrase = "";
        var minutesPhrase = "";

        if(seconds > 0) {
            minutes = minutes + 1;
            if(minutes == 60) {
                hours = hours + 1;
                minutes = 0;
            }
        }

        if(hours == 1) {
            hoursPhrase = "1 hour";
        } else {
            if(hours > 0) {
                hoursPhrase = `${hours} hours`;
            }
        }

        if(minutes == 1) {
            minutesPhrase = "1 minute";
        } else {
            if(minutes > 0) {
                minutesPhrase = `${minutes} minutes`;
            }
        }

        waitTime = `Current wait time may be more than${hoursPhrase} ${minutesPhrase}`;

    }

    console.log(waitTime);

    return callback(null, waitTime)

}; 

This function sets the waitTime variable based on the value of the wait time (in seconds) passed into the function. Three conditions are used to assign a value to waitTime based on the current EWT.

  • The first condition is true when EWT is less than a low threshold (such as two minutes).
  • The second is true when theEWT is greater than a high threshold (such as two hours).
  • The third is the default path, which is taken when the EWT is between the upper and lower threshold.

You can customize the phrase or alter the thresholds as needed for your integration.

Create a Retrieve Widget Status Function (getWidgetStatus)

We recommend checking the status of the associated Mindful Call Target before transferring a call from Twilio Flex to Mindful. You can check the Mindful status by using a Twilio Function to invoke the Mindful API's Retrieve Widget Status endpoint.

Following is an example of a getWidgetStatus function.

// GET Widget State from Mindful Digital.

// Add axios 0.20.0 as a dependency under Functions Settings, Dependencies
const axios = require('axios');

exports.handler = function (context, event, callback) {
  let twiml = new Twilio.twiml.VoiceResponse();

  const widget_url = event.widget_url;
  
  axios
    .get(widget_url, 
    {
    headers: {
      'Content-Type': 'application/json',
   },
  })
    .then((response) => {
      console.log(JSON.stringify(response.data));
      return callback(null, response.data);
    })
    .catch((error) => {
      console.log(error);
      return callback(error);
    });
};
NOTE
This sample Function uses the axios Javascript library. You can add this library to the service at Service > Settings > Dependencies. To add axios to the dependencies, enter axios as the module and 0.20.0 as the version.


The input for this Function is the Mindful widget API URL found on the Widgets page of the Mindful Callback user interface. The output contains information related to a specified Mindful Scheduler widget, which in turn is associated with a specific Mindful Call Target. In the Inbound Studio Flow, we will parse the widget_state value returned from the API to determine whether to send the call to Mindful or not.

(Optional) Create a Mindful Datastore Post Function (datastorePost)

There are two methods available to pass data between Mindful and Twilio Flex during inbound calls and callbacks. The preferred method is to use Mindful Datastore to store and retrieve KVPs via API. Alternatively, you could pass custom SIP headers containing user data to Mindful, and those headers would be attached to callbacks returning to the Twilio environment.

Following is a sample function to post data to Mindful Datastore.

// POST to the Mindful Datastore service.

// Add axios 0.20.0 as a dependency under Functions Global Config, Dependencies
const axios = require('axios');

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

  const ds_url = event.DS_URL;
  const ds_auth = event.DS_Auth;
  const ds_body = event.DS_Body;

  const instance = axios.create({
    baseURL: ds_url,
    timeout: 3000,
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + ds_auth
    },
  });

  instance
    .post('/', ds_body)
    .then((response) => {
      console.log(JSON.stringify(response.data));
      return callback(null, response.data);
    })
    .catch((error) => {
      console.log(error);
      return callback(error);
    });
};

As with the Retrieve Widget Status function, this sample code uses the axios library for the HTTP client functionality.

The Datastore Post function takes three inputs from the Inbound Studio flow:

  • The URL for the Datastore API endpoint
  • The API Token for your Data Set Template
  • The body of the request (a JSON-formatted string containing the data to store)

The URL and API token can be found in the Datastore user interface. We will see an example of the request body later in this guide, when we configure the Inbound Studio Flow.

(Optional) Create a Mindful Datastore Get function (datastoreGet)

After posing data to Mindful Datastore, you will need another function to retrieve the data when a callback returns to the Twilio environment. This function will be used in the Return Call Studio Flow to parse the data before connecting an agent.

When requesting data from the Datastore, you will use the customer phone number as the search key. If the caller requests a callback to a different number than the ANI of the inbound call, Mindful Callback will update the Datastore record with the new ANI value and use that ANI as the SIP From address when placing the return call to Twilio.

Following is an example Datastore Get function.

// GET from the MINDFUL Mindful Datastore service.

// Add axios 0.20.0 as a dependency under Functions Settings, Dependencies
const axios = require('axios');

exports.handler = function (context, event, callback) {
  let twiml = new Twilio.twiml.VoiceResponse();

  const ds_url = event.DS_URL;
  const ds_auth = event.DS_Auth;
  
  axios
    .get(ds_url, 
    {
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + ds_auth
   },
  })
    .then((response) => {
      console.log(JSON.stringify(response.data));
      return callback(null, response.data);
    })
    .catch((error) => {
      console.log(error);
      return callback(error);
    });
};

Again, this sample function uses the axios library.

The inputs for this function are (1) the Mindful Datastore URL, which includes the customer ANI as a search parameter, and (2) the API Token for your Data Set Template.

Create a Normalize ANI function (normalizeANI) if you are using Mindful Datastore

If you are not using Mindful Datastore, this function is not required.

When Mindful sends the return call to Twilio, the ANI (Trigger.Call.From in Studio) is formatted as a SIP URI (such as sip:+13305554444@54.32.10.99). To be able to use the Datastore Get function, you will need to extract the phone number from the URI. Remember that the customer phone number will be used as the search key to retrieve any stored data.

The following shows an example normalizeANI function.

// This converts the format of the From address as sent with a Mindful Callback return call 
// (e.g. sip:+13305554444@54.32.10.99), and converts it to an 11-digit number

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

  const from = event.From;
  
  console.log("**** Passed in number is", from)
  
  var numReg = /sip:(?:\+)?([\d]{11})/;
  var match = numReg.exec(from);
  var ani = match[1];
  
  console.log("**** ANI result is",ani);
  
  return callback(null, ani);
};

The input for this function is the From number passed in from the Return Call flow. The output is the parsed number that can then be used in the Return Call flow.

NOTE
The example function returns an 11-digit number (for North America). You can change the function as needed for other regional formats.

Create Twilio Studio Flows

Two Studio Flows are needed for this integration; one to route inbound calls to Mindful for a callback offer and another to handle return calls coming from Mindful back to the contact center. This section of the guide will walk through the details of two example Flows, showing the important Flow widgets along the way.

Example Inbound Flow

This flow is typically a modification to the existing flow used to process inbound calls and queue to agents. These modifications will:

  • Fetch the EWT and turn it into a phrase to play back to customers.
  • Check the EWT to make sure it is above a defined threshold before offering a callback.
  • Play the EWT phrase to customers.
  • Check Mindful Callback status.
  • (Optional) Post data to Mindful Datastore.
  • Transfer the call to Mindful Callback via TwiML Redirect.

inbound flow diagram

For more detail on key steps in the Flow, see the sections below.

GetWaitTime (Run Function)

example of a get wait time block

The GetWaitTime Run Function widget invokes the getEWT function created earlier. Two parameters are passed into the function:

  • The SID of the Task Queue used to answer calls to this flow
  • The SID of the Workspace with which the Task Queue is associated

CheckWaitTime (Split Based On…)

This widget takes a real-time statistic parsed from the output of the GetWaitTime widget (in this example {{widgets.GetWaitTime.parsed.longestRelativeTaskAgeInQueue}}) and compares it against a defined minimum threshold.

example of a check wait time block

example of a check wait time block

In this example, if the wait time is less than two minutes (120 seconds), the call is routed to the SendToQueue widget without Mindful Callback treatment. If the wait is higher than two minutes, the call is sent to the GetWidgetState widget to continue with the Mindful call flow.

GetWidgetState (Run Function)

This Run Function widget invokes the GetWidgetState function created in a prior step. The function uses the Mindful API to check the status of the Call Target associated with the current queue.

example of a get widget state block

The input for this function is the widget_url, which is the full URL of the Retrieve Widget Status API endpoint (found on the Widgets page in the Mindful Callback UI). The full URI will look something like this example:

https://123454b312c678c4.cbridgert-stage.Mindfulcloud.com/api/widget/f3abac3eab34881fd3ce123456789

The output is a list of statistics and status parameters related to the widget and its Call Target. For this example, we are only intrested in the widget_state parameter, which we will use in the next step.

CheckWidgetState (Split Based On…)

This conditional widget checks the widget_state value parsed by the GetWidgetState function ({{widgets.GetWidgetState.parsed.widget_state}}). The value is compared in the Transitions tab, as seen below.

example of a check widget state block

example of a check widget state block

In this example, if widget_state returns either offer_callback or offer_asap_callback, then the Flow will continue with Mindful logic. If neither of these values are returned, it is assumed that the Call Target is not available to register callbacks. In that case, the Flow will send the call to the SendToQueue widget next.

ConvertEWT (Run Function)

The ConvertEWT Run Function widget invokes the convertEWT function. The function takes a real-time statistic provided by the GetWaitTime widget ({{widgets.GetWaitTime.parsed.longestRelativeTaskAgeInQueue}}) and outputs a phrase that can be played back to the caller.

example convert E.W.T. block

PlayEWTPhrase (Say/Play)

This PlayEWTPhrase Say/Play widget takes the output from the ConvertEWT widget ({{widgets.ConvertEWT.body}}) and plays it back to customers using text-to-speech. This helps customers to make informed decisions when offered a callback.

example play E.W.T. block

SetVariables (Set Variables)

This SetVariables widget defines data to be used in later steps. Data defined in this widget will be used in Mindful Datastore POST requests and in transfers to Mindful Callback. Consult the image and notes below when configuring this widget.

example set variables block

  • FirstName, LastName, AccNum: These example values match our example Data Set Template in Mindful Datastore. The values used in your integration will differ based on your particular strategy. The account number (AccNum) is also used in the example TwiML Redirect step to pass the account number via custom SIP header (an alternative to Datastore).
  • CallId: This is the Twilio Call ID for the inbound call. The CallId can be passed to Mindful Callback to be passed back on the Return call. You might use this value in reporting, troubleshooting, or any other purpose. In this example Flow, we will pass the CallId to Mindful via the Datastore and attach it as a custom SIP header (showing both alternatives). To use this Call ID in a Flow, reference the variable {{trigger.call.CallSid}}.
  • MindfulNumber: This is the Mindful Call Target phone number, to which this call will be transferred. This variable is used in the TwiML Redirect step, rather than a hard-coded value, to allow the TwiML Bin to be reused for multiple Call Targets with different numbers.

SetDSVars (Set Variables)

If you plan to use Mindful Datastore to pass data between Twilio and Mindful, you'll need to set a few variables for the Datastore Post.

example set D.S. vars block

The variables set here are:

  • DS_Body: The JSON-formatted body containing all the data to be sent to Mindful Datastore
  • DS_URL: The full Datastore URL
  • DS_Auth: The API Token associated with your Dataset Template in the Mindful Datastore

The value of DS_Body must be formatted as follows:

example set D.S. vars block

The body must start with “customer_contact_number”:"{{trigger.call.From}}" followed by a data_values section matching your Data Set Template in Mindful Datastore. In this example, the Data Set Template contains FirstName, LastName, AccNum and CallId. Yours will differ depending on the data keys you have configured.

For convenience, you can copy the example body below as a starting point.

{"customer_contact_number":"'{{trigger.call.From}} ","data_values":{"FirstName":"{{flow.variables.FirstName}}","LastName":"{{flow.variables.LastName}}","AccNum":"{{flow.variables.AccNum}}","CallId":"{{flow.variables.CallId}}"}}

(Optional) DatastorePost (Run Function)

This Run Function widget is only required when using the Datastore to pass data between the inbound and return calls. The DatastorePost widget invokes the datastorePost function and the input parameters are the {{flow.variables.DS_Auth}}, {{flow.variables.DS_URL}}, and {{flow.variables.DS_Body}} variables set in the previous Set Variables widget.

example datastore post block

Quick copy:

  • {{flow.variables.DS_Auth}}
  • {{flow.variables.DS_URL}}
  • {{flow.variables.DS_Body}}

XferToMindful (TwiML Redirect)

This TwiML Redirect widget transfers the call to Mindful. Instead of using a Connect Call To widget, we use a TwiML Redirect widget. This way, if a customer declines a callback offer, Mindful can issue a SIP REFER to Twilio and resume the Inbound Flow from this TwiML Redirect widget.

example transfer to mindful block

The URL consists of:

  • The TwiML Bin URL created previously
  • The mindful_number parameter referenced in the TwiML Bin, with the value being the MindfulNumber variable set previously in this flow
  • Account Number (accnum) and Call ID (callid) parameters referenced in the example TwiML bin, with values from the AccNum and CallId variables set previously in this flow.
  • The routingtoken parameter will be used to populate the X-Mindful-Routing-Token SIP header required in the SIP INVITE to Mindful Callback.

The full URL for this example flow can be seen below.

https://handler.twilio.com/twiml/EHea0d3769be5f4xxxxxxxxxxxxxxx?mindful_number={{flow.variables.MindfulNumber}}&accnum={{flow.variables.AccNum}}&callid={{flow.variables.CallId}}&routingtoken={{flow.variables.MindfulToken}}

If a transfer to Mindful fails or a customer declines a callback offer, the call will continue to the next widget, which will likely be a SendToQueue (Send to Flex) widget.

This concludes the Inbound Flow! Next, we will cover an example Return Call Flow to handle callbacks returning to the Twilio environment.

IMPORTANT
After saving and publishing the Inbound flow, update the TwiML Bin created earlier. You will need to add the URL of the Inbound flow now that it is available.



Example Return Call Flow

The Return Call Flow is invoked from the SIP Domain created previously. This flow contains up to five key widgets, depending on whether you plan to use Mindful Datastore or not.

return call flow diagram

For more detail on key steps in the Flow, see the sections below.

(Optional) NormalizeANI (Run Function)

This function is only required when using Mindful Datastore. Since the Twilio From parameter will be formatted as a SIP URI, while the Datastore Get requires the phone number as a search parameter, this function takes the SIP URI and returns only the phone number portion.

example normalize ani block

The NormalizeANI Run Function widget invokes the normalizeANI function, using the system ANI {{trigger.call.From}} as the input parameter value (with a key name of From to match the input name inside the Function).

(Optional) SetDSVars (Set Variables)

This optional widget, again only required if using Mindful Datastore, sets the two parameters required for the Datastore Get widget. The two input parameters are:

  • DS_Auth: The API Token for your Data Set Template in Mindful
  • DS_URL: The full Datastore URL with the customer ANI appended as a query string parameter:
    • <URL>?customer_contact_number={{widgets.function_NormalizeANI.body}}

See the example images below for more context:

example set D.S. vars block

example set D.S. vars block

(Optional) DatastoreGet (Run Function)

The DatastoreGet widget invokes the datastoreGet function to retrieve data from Mindful Datastore for return calls. The two input parameters for this function come from the SetDSVars widget configured in the prior step:

  • DS_URL: {{flow.variables.DS_URL}}
  • DS_Auth: {{flow.variables.DS_Auth}}

screenshot of the datastore get widget

SetVars (Set Variables)

If you passed data to Mindful during the inbound call, the returned data can be assigned to variables for use in screen pop, reporting, or routing decisions.

The following examples show the format to set variables based on SIP headers or a Datastore Get request. Remember that only one method (if any) will be used.

Option 1) SIP header example

Here we reference a SIP header named X-inboundcallid: via {{trigger.call.SipHeader_<name>}}.

screenshot of the set vars widget

Option 2) Datastore example

Here we reference an AccNum parameter retrieved from Datastore via {{widgets.DatastoreGet.parsed.<name>}}:

screenshot of the set vars widget


(Optional) Step 3: Configure Second Chance Callback

After a customer declines a callback offer, you can offer that customer a second chance (or more) to register a callback while waiting in a Flex queue. You can do this by adding a new Send to Flex widget in the Inbound Studio Flow, with the URL of a new hold-treatment function in the Music URL field of that widget.

Second-Chance overview

This section of the guide describes changes to the Studio Flow and functions to incorporate Second-Chance Callback:

  • One new Mindful Call Target for each existing Call Target for which Second-Chance will be used. You can then disable the offer on the second Call Target to ensure that registration begins immediately.
  • Two new Twilio functions:
    • One function to play queue music/announcements and make subsequent callback offers
    • Another function to check for customer input and exit the Flex queue when an offer is accepted
  • Modifications to the Inbound Flow:
    • A new Send To Flex widget containing the URL of the hold treatment function as the Hold Music TWIML URL
    • A new Connect Call To widget to transfer the call to the number of the new Second-Chance Call Target in Mindful
    • Change the connection from the Return exit of the existing Mindful Transfer (TwiML Redirect) to point to the new Send to Flex widget (SecondChanceQueue)

Create new Twilio functions

Two new functions are needed; one to play hold treatment, including the callback offer, and the other to check for customer input and exit the queue if valid input is detected. This section will detail two example functions to accomplish this.

Hold Treatment function

This example function plays music and offers a callback. The function uses Twilio’s Gather verb to collect input while invoking a separate function (transfer_to_mindful) to check the input for DTMF 1.

/**
 * Plays Music and then offers Callback 
 * This will loop until call is answered, or caller presses 1 for callback option
 */
exports.handler = function(context, event, callback) {
    let twiml = new Twilio.twiml.VoiceResponse();
    twiml.play('https://demo.twilio.com/docs/classic.mp3')
    let action_url = "https://" + context.DOMAIN_NAME + '/' + 'transfer_to_mindful';
    twiml.gather({action: action_url})
        .say('Press 1 if you would like to be called back, instead of waiting');

return callback(null, twiml);
};
NOTES
  • It may be worth breaking up the music file to segments that have a duration matching the time you want the music to play before offering a callback.
  • Also, as this function is configured as a Music URL in the Send to Flex widget, it will repeat for as long as the call is in queue.


Transfer to Mindful function

This function is invoked by the previous hold treatment function after offering a callback. If the DTMF 1 is confirmed, the function takes the call out of the Flex queue and exits the Send to Flex (SecondChanceQueue) widget in the Inbound Flow. The call moves to the new Connect Call To (SecondChanceCallback) widget. If the caller does not provide any input, the call will continue queueing as normal.

/**
 * Checks entered digits and exits queue if digit 1 is passed. 
 * If dtmf 1 is not passed, returns to hold treatment function
 */
exports.handler = function(context, event, callback) {
    let response = new Twilio.twiml.VoiceResponse();

    let gathered_digits = 0;
    if (event['Digits']) {
        gathered_digits = parseInt(event['Digits']);
    }
    if (gathered_digits == 1) {
        // leave Flex queue and continue with next node connected to Task completed
        response.leave();
    }

    return callback(null, response);

};

Edit the Inbound Flow

After publishing the new Second-Chance functions, you will need to make a few changes to the Inbound Studio Flow as detailed below. The changes may look something like the following example:

example modifications to the inbound flow

New Send to Flex widget

Create a new Send to Flex widget to queue to Flex with hold treatment including the Second Chance Callback offer. This will only be used for calls exiting the existing TwiML Redirect (XferToMindful) via the Return exit. Calls queueing for any other reason (EWT below threshold, Mindful unavailable, or any widget errors) should still use the Send To Flex widget that does not include the Second-Chance offer.

example second chance queue block

This Send to Flex widget will be identical to the existing Send to Flex widget, except that the URL of the new Hold Treatment function is added to the HOLD MUSIC TWIML URL field.

New Connect Call To widget

This new widget is used when a call exits the new Send to Flex (SecondChanceQueue) widget from the Task Created exit (when the Transfer to Mindful function confirms caller input for Second Chance Callback).

This widget should be set to connect the call to a SIP Endpoint, and the SIP Endpoint should contain the URL for the new Second-Chance Call Target in Mindful.

connect call to

NOTES

This example shows a SIP endpoint of sips:285140005@sip-callack.mindful.cx.net:5061?X-accnum={{flow.variables.AccNum}}

  • We are using the sips: prefix and port of 5061 to use TLS for the SIP Invite to Mindful Callback. Use sip: and 5060 for non-TLS.
  • The example number 285140005 is different than the number passed into the TwiML Redirect for the initial callback offer. This is because a different Call Target should be used for Second Chance Callback.
  • The URL sip-callback.mindful.cx.net may be different for your integration. This should be the same FQDN used in the TwiML Redirect.
  • This example also shows the custom sip header X-accnum being set to show that SIP headers can be used with the Second Chance Callback transfer.



Appendix A – Example cached getEWT Function

As mentioned previously in this guide, Twilio recommends that caching be leveraged for any functions that make use of the Twilio Task Router APIs, to allow for scaling. Here is an example getEWT function using the Function Service Environment Variables as the cache.

Firstly, using the same Function Service set up previously in this guide, create a new function for the cached getEWT – here is an example:

/**
 * Function to read longestRelativeTaskAgeInQueue statistics from TaskRouter's real-time statistics.
 * https://www.twilio.com/docs/taskrouter/api/taskqueue-statistics#taskqueue-realtime-statistics
 *
 * It returns JSON object with following fields:
 * - longestRelativeTaskAgeInQueue - number of seconds
 *
 * Expected variables from context:
 * - Queue_EWT - initial value of 0, used by script to cache value of average task acceptance time
 * - Queue_EWT_Last_Updated - initial value of 0, used by script to cache timestamp of last update of
 * - Queue_Update_Interval - average time update interval in milliseconds, initial value of 60000
 * - Workspace_SID - the SID of the Workspace containing the Task Queue
 * - Task_Queue_SID - the Task Queue to query for real-time statistics
 * - SERVICE_SID - the SID of the server this function belongs to
 * - ENVIRONMENT_SID - SID of the the Environment variables used by this Service/Function
 * - Queue_EWT_Last_Updated_SID - SID of variable Queue_EWT_Last_Updated
 * - Queue_EWT_SID - SID of variable Queue_EWT
 *
 * Following twilio-cli calls are useful for setting up environment variables for this script:
 *
  * twilio api:serverless:v1:services:list
 *
 * twilio api:serverless:v1:services:environments:list \
 *     --service-sid ZSXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
 *
 * twilio api:serverless:v1:services:environments:variables:list \
 *     --service-sid ZSXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \
 *     --environment-sid ZEXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
 */

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

    const response = new Twilio.Response();
    response.appendHeader('Access-Control-Allow-Origin', '*');
    response.appendHeader('Access-Control-Allow-Methods', 'OPTIONS POST');
    response.appendHeader('Content-Type', 'application/json');
    response.appendHeader('Access-Control-Allow-Headers', 'Content-Type');

    get_wait_time(context, event, callback).then(value => {
        response.setBody({
                             'longestRelativeTaskAgeInQueue': value
                         });
        return callback(null, response);
    })

}

async function get_wait_time(context, event, callback) {
    const client = context.getTwilioClient();

    let current_timestamp = new Date().getTime();

    if ((current_timestamp - context.Queue_EWT_Last_Updated) > context.Queue_Update_Interval) {

        let longest_relative_task_age_in_queue = await get_queue_real_time_statistics(client, context.Workspace_SID, context.Task_Queue_SID, 'longestRelativeTaskAgeInQueue');

        context.Queue_EWT = parseInt(longest_relative_task_age_in_queue);

        await client.serverless.services(context.SERVICE_SID)
            .environments(context.ENVIRONMENT_SID)
            .variables(context.Queue_EWT_SID)
            .update({
                        key: 'Queue_EWT',
                        value: longest_relative_task_age_in_queue
                    });
        await client.serverless.services(context.SERVICE_SID)
            .environments(context.ENVIRONMENT_SID)
            .variables(context.Queue_EWT_Last_Updated_SID)
            .update({
                        key: 'Queue_EWT_Last_Updated',
                        value: current_timestamp
                    });

    }

    return context.Queue_EWT;
}

async function get_queue_real_time_statistics(twilio_client, workspace_sid, task_queue_sid, stat_name) {
    return twilio_client.taskrouter.workspaces(workspace_sid)
        .taskQueues(task_queue_sid)
        .realTimeStatistics()
        .fetch()
        .then(stats => {
            return (stats[stat_name]);
        });
}

For this function to execute correctly, some environment variables must be created in the same Service as the Cached EWT function. On the Function page, inside the Service, click on Environment Variables in the Settings section.

If you receive the following error during execution, go to service dependencies and update the twilio library's version (e.g., to *) and redeploy.

UnhandledPromiseRejectionWarning: Unhandled promise rejection: TypeError: 
Cannot read property 'services' of undefined at get_wait_time
(/var/task/handlers/ZN016166710a27ef5a1f9efa721c2809e2.js:40:33) at
processTicksAndRejections (internal/process/task_queues.js:97:5)

Seven environment variables are used in this example function, so add the seven new environment variables as shown here:

screenshot of environment variables

These variables are described below:

  • Queue_Update_Interval: This is the time (in millseconds) that the function will use to determine whether to query the Task Router API or not. It does this by comparing the current time (during each execution of the function) against the last time the statistic, and if the time difference has exceeded the configured interval, it will fetch the statistic from the Task Router API. If the time difference since the last update has not exceeded the interval, it will use the cached value (Queue_EWT variable) instead. The example interval value shown here is set to 20000 which is 20 seconds.
  • Queue_EWT: This is the environment variable in which the wait time value (in seconds) retrieved from the Task Router API is stored. This should be set to 0 on creation.
  • Queue_EWT_SID: When a function retrieves a value from an environment variable, it uses the variable name – e.g. context.Queue_EWT. However, for the function to be able to write to the environment variable, it must use the Environment Variable’s SID (see note below on how to retrieve the SID of an Environment Variable), so the SID for the Queue_EWT environment variable should be used as the value for this variable.
  • Queue_EWT_Last_Updated: This variable stores the unix timestamp of the last time the EWT was retrieved from the Task Router API and updated into the Queue_EWT variable.
  • Queue_EWT_Last_Updated_SID: This variable contains the SID of the Queue_EWT_Last_Updated variable to allow the function to be able to write the timestamps to that variable. As with Queue_EWT_SID, see below for more information on how to retrieve the SID for an environment variable.
  • Workspace_SID: This variable contains the SID of the Workspace containing the Task Queue for these calls.
  • Task Queue SID: This variable contains the SID of the Task Queue used to queue calls using this function.

The SID values for the Workspace and Task Queue can be found in the relevant sections of the Twilio console, however retrieving the SID for the environment variables must be done via the Twilio CLI. Please see the official Twilio documentation for more information on installing and using the Twilio CLI – an example of Twilio CLI (within Windows Powershell) being used to retrieve the SID of the environment variables is shown here:

example environment variables in powershell

The two commands used are:

twilio api:serverless:v1:services:environments:list \ 
      --service-sid ZSXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

This command lists the environments available under the service, the SID of the service can be found using the Twilio console (or via CLI command). Once the environment SID is retrieved, this can be used along with the same service SID in the following command:

twilio api:serverless:v1:services:environments:variables:list \
     --service-sid ZSXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \
     --environment-sid ZEXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

This will list all environment variables under the service/environment.

As the example function uses environment variables with static values specific to the Workspace and Task Queue, you will need to consider how to adapt this to use with multiple queues – for example, one approach could be to create a Function Service for each Queue, each service containing its own copy of the cashed getEWT function, and its own set of Environment Variables specific to that queue.


Was this article helpful?

What's Next
Changing your password will log you out immediately. Use the new password to log back in.
First name must have atleast 2 characters. Numbers and special characters are not allowed.
Last name must have atleast 1 characters. Numbers and special characters are not allowed.
Enter a valid email
Enter a valid password
Your profile has been successfully updated.