1

I am using Twilio Flex to support a call center. I have a TaskRouter workflow set up where Task Reservation Timeout is set to 120 seconds. In its filter, I've created two routing steps. The first one finds matching workers in the main queue and has a timeout of 120 seconds. After 120 seconds, it should move to Call Forward Queue. In the call forward queue, no workers exist (target worker expression: 1==2). I'm catching all these events with a "trEventListener" function. Once a task is moved into the Call Forward queue, I call the "callForward" function which uses twiml.dial() to connect the call to an external number. I also change this task's status to "canceled" with a custom reason so I can track it in flex insights. I am using the guide in this link to form my logic: https://support.twilio.com/hc/en-us/articles/360021082934-Implementing-Voicemail-with-Twilio-Flex-TaskRouter-and-WFO.

Call forwarding is working fine but according to Flex insights, there are some calls that get handled after 120 seconds (between 120 - 300 seconds). Ideally, these should be forwarded as well. There is also no error logged for me to track down why this is happening to only a handful of calls.

Furthermore, in some cases, when I try to change the task status to cancel with my custom reason, it spits out the following error: Cannot cancel task because it is not pending or reserved. In other cases, it works fine. It's again hard to figure out why it's selectively working and not consistent in its behavior.

Here is the function code.

trEventListener.js:

exports.handler = function(context, event, callback) {
    
    const client = context.getTwilioClient();
    let task = '';
    let workspace = '';
    console.log(`__[trEventStream]__: Event recieved of type: ${event.EventType}`);
    
    // setup an empty success response
    let response = new Twilio.Response();
    response.setStatusCode(204);
    
    // switch on the event type
    switch(event.EventType) {
        case 'task-queue.entered':
            // ignore events that are not entering the 'Call Forward' TaskQueue 
            if (event.TaskQueueName !== 'Call Forward') {
                console.log(`__[trEventStream]__: Entered ${event.TaskQueueName} queue - no forwarding required!`);
                return callback(null, response);
            }
    
            console.log(`__[trEventStream]__: entered ${event.TaskQueueName} queue - forwarding call!`);
            task = event.TaskSid;
            workspace = event.WorkspaceSid;
            const ta = JSON.parse(event.TaskAttributes);
            const callSid = ta.call_sid;
    
            let url = `https://${context.DOMAIN_NAME}/forwardCall`;
    
            // redirect call to forwardCall function
            client.calls(callSid).update({
                method: 'POST',
                url: encodeURI(url),
            }).then(() => { 
                console.log(`__[trEventStream]__: [SUCCESS] ~> Task with id ${task} forwarded to external DID`);
                // change task status to canceled so it doesn't appear in flex or show up as a pending task
                client.taskrouter.workspaces(workspace)
                 .tasks(task)
                 .update({
                    assignmentStatus: 'canceled',
                    reason: 'Call forwarded'
                  })
             .then(task => {
                    console.log(`__[trEventStream]__: [SUCCESS] ~> Task canceled`);
                    return callback(null, response); 
                 }).catch(err => { 
                    console.log(`__[trEventStream]__: [ERROR] ~> Task not marked complete: `, err);
                    // doesn't warrant reponse 500 since call still forwarded :)
                    return callback(null, response);
                 });
            }).catch(err => {
                console.log(`__[trEventStream]__: [ERROR] ~> Task failed to forward to external DID: `, err);
                response.setStatusCode(500);
                return callback(err, response);
            });
    break;
    default:
        return callback(null, response);
    }
};

callForward.js:

exports.handler = function(context, event, callback) {
    console.log(`forwarding call`);
    // set-up the variables that this Function will use to forward a phone call using TwiML
    // REQUIRED - you must set this
    let phoneNumber = event.PhoneNumber || context.NUMBER;
    // OPTIONAL
    let callerId =  event.CallerId || null;
    // OPTIONAL
    let timeout = event.Timeout || null;
    // OPTIONAL
    let allowedCallers = event.allowedCallers || [];

    let allowedThrough = true;
    if (allowedCallers.length > 0) {
      if (allowedCallers.indexOf(event.From) === -1) {
        allowedThrough = false;    
      }
    }
    
    // generate the TwiML to tell Twilio how to forward this call
    let twiml = new Twilio.twiml.VoiceResponse();

    let dialParams = {};
    if (callerId) {
      dialParams.callerId = callerId;
    }
    if (timeout) {
      dialParams.timeout = timeout;
    }
    
    if (allowedThrough) {
      twiml.dial(dialParams, phoneNumber); // making call :)
    }
    else {
      twiml.say('Sorry, you are calling from a restricted number. Good bye.');
    }
    
    // return the TwiML
    callback(null, twiml);
        
};

Any kind of help and/or guidance will be appreciated.

Hamza
  • 13
  • 1
  • 3
  • I'm not exactly sure on either question. Perhaps [this will help give you an idea about tasks and their timeouts](https://www.twilio.com/docs/taskrouter/task-evaluation). As for the failed update on the task, my guess is that forwarding the call away from the task causes it to cancel, but there's a race condition between Twilio cancelling the task and your code, which is why you don't always see the error. – philnash Mar 29 '21 at 08:01
  • Thanks for the response and the resources. I will investigate further and give an update if I am able to figure it out. – Hamza Mar 29 '21 at 08:20
  • @philnash you are correct - this is happening because of a race condition b/w me canceling the task and Twilio canceling the task. When you use `twiml.dial()` to redirect a task, the default behavior is to cancel that task with the reason *"redirected"*. – Hamza Mar 31 '21 at 07:44
  • Ah cool, glad you worked that part out! – philnash Mar 31 '21 at 07:46
  • 1
    @philnash So there are two cases when a call will be moved to the call forward queue: 1. No matching worker is found and the timer hits 120 seconds 2. A matching worker is found before 120 seconds, the timer starts from 0 again and if the agent doesn't pick call within 120 seconds - it will be redirected. But if the agent picks a call the overall wait time will be = **time it took find matching worker + time matching worker took to answer the call** and this total can be well over 2 minutes. This is why flex insights was showing calls with wait times over 120 seconds. – Hamza Mar 31 '21 at 07:47
  • Ah, ok, so it's down to the addition of task queue timeout and reservation timeout. I guess you can adjust each to make the wait time shorter depending on what works best for you. – philnash Mar 31 '21 at 07:49
  • My use case is to redirect the call if it is not attended in 120 seconds. Reducing the timeout doesn't really help because that could lead to scenarios where the call is being redirected in under 120 seconds. I need to figure a way out so that irrespective of whether a matching worker is found or not - the total commutative waiting time should never exceed 120 seconds. As soon as it's 120 - it should immediately redirect. Might have to play around with twilio functions to achieve this. – Hamza Mar 31 '21 at 07:55

1 Answers1

0

Twilio developer evangelist here.

When you redirect a call from a task, its task is cancelled with the reason "redirected" so you don't need to cancel it yourself.

Your code was failing to update the task occasionally because of a race condition between your code and the task getting cancelled by Twilio.

philnash
  • 70,667
  • 10
  • 60
  • 88