Heyo, Twilio Developer Evangelist here. 👋
I just set down and built your use case in a single function. There is a lot going on inside of this one function (and I might recommend splitting the work into several functions).
I'm happy to answer your bonus questions, too, but please open it separately to keep this answer focused. :)
Let's have a look at a working example!
const moment = require('moment-timezone');
function isServiceOpen() {
let now = moment().tz('Australia/Brisbane');
let isWorkday = now.isoWeekday() < 6;
let isWorkingHour = now.hour() > 7 && now.hour() < 17;
return isWorkday && isWorkingHour;
}
exports.handler = function(context, event, callback) {
const twiml = new Twilio.twiml.VoiceResponse();
console.log(event);
const callIncludesRecording = !!event.RecordingUrl;
const callWasAnswered = event.DialCallStatus === 'completed';
const callWasNotAnswered = event.DialCallStatus === 'no-answer';
const serviceIsOpen = isServiceOpen();
if (callWasAnswered) {
twiml.hangup();
} else if (callIncludesRecording) {
console.log('Call includes recording!');
// do something with the recording URL here
console.log(event.RecordingUrl);
twiml.say("Thank you! We'll come back to you shortly.");
twiml.hangup();
} else if (callWasNotAnswered) {
console.log('Call was not answered...');
twiml.say(
'Unfortunately no one can answer right now. But you can leave a message.'
);
twiml.record({
action: '/handle-call'
});
} else if (!serviceIsOpen) {
console.log('Service is closed...');
twiml.say('Service is closed but you can leave a message');
twiml.record({
action: '/handle-call'
});
} else {
twiml.dial(
{
action: '/handle-call',
method: 'POST',
timeout: 5
},
'+4915...'
);
}
callback(null, twiml);
};
The function you see above is available under a /handle-call
endpoint and answers all webhooks for a call.
Scenario 1 - a call is not answered
At the end of the function, you see the dial
function call. The important piece for this case is that dial
supports a timeout
and an action
attribute.
twiml.dial(
{
action: '/handle-call',
method: 'POST',
timeout: 30
},
'+49157...'
);
The above tells Twilio to try to call the number +49157...
for 30 seconds (it's actually closer to 35 – you can read details in the docs). If the call ends or no one answered the call until the timeout is reached Twilio will ask the defined action
URL for additional TwiML configuration.
The URL in the action
attribute references the same function path (/handle-call
) and the same function will be executed again but this time the event
object will include a DialCallStatus
of no-answer
(have a look at the variable callWasNotAnswered
). If the call was not answered you can return TwiML to say a message and tell the API to start the recording of the call.
// no one answered – let's record
twiml.say(
'Unfortunately, no one can answer right now. But you can leave a message.'
);
twiml.record({
action: '/handle-call'
});
The record
verb also allows an action
attribute which lets you define a URL that should be requested when the recording finished (we'll use again the same function under the same /handle-call
endpoint).
The call to the same URL will then include a RecordingUrl
inside of the event
object. If this property is present you know that it is the result of the recording. At this stage, it is time to do something with the recording URL (send a message, log it, ...), to finish the call after saying "goodbye" and to hang up.
// do something with the recording URL here
console.log(event.RecordingUrl);
twiml.say("Thank you! We'll come back to you shortly.");
twiml.hangup();
The webhook flow is as follows:
POST
/handle-call
(initial webhook) -> dial +49157...
POST
/handle-call
(call after time out) -> say "unfortunately, ..." & record
POST
/handle-call
(recording finished) -> say "thank you" & hangup
Scenario 2 - the call is outside of business hours
For this scenario, I took the logic that you already provided and created a isServiceOpen
helper function. When a call comes in outside of business hours the function responds with TwiML defining a message and a recording.
twiml.say('Service is closed but you can leave a message');
twiml.record({
action: '/handle-call'
});
After the finished recording the our function will be called (/handle-call
). This time, the request will include a RecordingUrl
and the handling will be the same as in scenario 1 (log the recording URL and hangup).
The webhook flow is as follows:
POST
/handle-call
(initial webhook) -> say "service is closed" & record
POST
/handle-call
(recording finished) -> say "thank you" & hangup
Scenario 3 – the call was answered
In case the call happens in business hours and was answered quickly enough there won't be a need for a recording. Because the dial verb includes an action
attribute (we used this in scenario 1) another webhook will be sent after the call is ended.
This webhook will include a DialCallStatus
parameter with the completed
value. Then it's time to end the call and hang up.
twiml.hangup();
POST
/handle-call
(initial webhook) -> dial "+49157..."
POST
/handle-call
(call finished) -> hangup
I hope the above helps. 😊 As mentioned initially, it might be a good idea to split the functionality into /handle-call
, /handle-recording
and other functions.
Let me know if that helps and you have any further questions. :)
callback(null, response)
in the first code block to be the Twiml Bin in the third code block, then how do I tell it to try for 20 seconds before diverting to voicemail? I'm guessing I should replacecallback("Service is closed");
in the first code block with theelse
statement in the second code block. Then I also have no idea at all about how to do the bonus question at the bottom. The documentation is very scattered and lacking for Twilio. – Jake<Sip>
can be found here). As fof functions and Twimlets we have a project to turn all Twimlets into Functions which should simplify that too. – philnash