I already had this function in my Twilio Functions section, although I'm not sure if I copy-pasted this from somewhere a while ago or if it came by default:
var moment = require('moment-timezone')
exports.handler = function(context, event, callback) {
let now = moment().tz('Australia/Brisbane');
let isWorkday = (now.isoWeekday() < 6);
let isWorkingHour = (now.hour() > 7 && now.hour() < 17);
let response = {};
if(isWorkday && isWorkingHour) {
callback(null, response);
} else {
callback("Service is closed");
}
};
I also found another SO post where someone included a basic function to divert to voicemail if a call is unanswered:
exports.handler = function(context, event, callback) {
const twiml = new Twilio.twiml.VoiceResponse();
if (event.DialCallStatus === 'completed' || event.DialCallStatus === 'answered') {
twiml.hangup();
} else {
twiml.say("Service is closed");
twiml.record({
transcribe: true,
transcribeCallback: "http://twimlets.com/[email protected]",
action: "/hangup"
});
}
callback(null, twiml);
};
What I want to do is basically combine these two so that:
!isWorkday || !isWorkingHour
then send straight to voicemail. Don't ring the phone at all.isWorkday && isWorkingHour
then run something like this Twiml bin:<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Dial>
<Sip>
[email protected];region=au1
</Sip>
</Dial>
</Response>
Bonus question: I obviously also need to be able to listen to the voicemail as I doubt the transcribing will be very accurate. Is there any way to include a link to the voicemail (or the voicemail mp3 itself) in the email I receive when a new voicemail is created? Or can I create a function/twiml bin for outgoing calls that would let me dial a number and listen to my voicemails, like how normal voicemail works??
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.
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, ..." & recordPOST
/handle-call
(recording finished) -> say "thank you" & hangupFor 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" & recordPOST
/handle-call
(recording finished) -> say "thank you" & hangupIn 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) -> hangupI 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. :)