Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(templates): add Personal Voicemail template #107

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added personal-voicemail/.env
Empty file.
89 changes: 89 additions & 0 deletions personal-voicemail/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# personal-voicemail

Provides a simple voice mailbox with SMS notifications and a block list. After callers leave a message you'll be sent a link to the recording via SMS.

An optional block list allows you to manually add phone numbers that you'd like to reject. Rejected callers are not allowed to leave messages.

## Pre-requisites

### Twilio Phone Number

You must own or purchase a Twilio Phone Number to use this function.

### Functions Configuration (Twilio Console)

This function requires that you have the "Enable ACCOUNT_SID and AUTH_TOKEN" preference set in your [Functions Settings](https://www.twilio.com/console/functions/configure).

### Function Parameters

`/personal-voicemail` has the following configuration options defined in the function source. The only option that is mandatory to configure the function is the `phoneNumber` input.

| Option | Description |
| :-------- | :---------- |
| `phoneNumber` | The phone number to which you'd like to forward incoming calls, formatted in E164. |
| `timeout` | The dial timeout. This is the amount of time that the function will allow your phone to ring before forwarding to voicemail. Default: 12 |
| `secureRecordingLinks` | By default recording urls link directly to the stored message. If you have enabled authentication on your media urls then your voicemail notifications will receive a link to recordings via the Twilio Console. Default: true |
| `voiceOpts` | Control the voice and language preference for the voice prompts. See the docs for details: https://www.twilio.com/docs/voice/twiml/say#attributes-voice |
| `voiceMailMessage` | Control the prompt for callers to leave you a message. Optionally this can be a full url to a recording that will be played instead of using text to speech. |
| `reject` | A list of E164 formatted phone numbers that will be rejected. Calls from these numbers will not be forwarded or allowed to leave a message. |
| `rejectMessage` | Control the prompt to rejected callers. Setting this value to `false` will hang up on blocked callers without informing them that they've been rejected. |


## Create a new project with the template

1. Install the [Twilio CLI](https://www.twilio.com/docs/twilio-cli/quickstart#install-twilio-cli)
2. Install the [serverless toolkit](https://www.twilio.com/docs/labs/serverless-toolkit/getting-started)

```
twilio plugins:install @twilio-labs/plugin-serverless
```

3. Initiate a new project

```
twilio serverless:init example --template=personal-voicemail && cd personal-voicemail
```

4. Start the server with the [Twilio CLI](https://www.twilio.com/docs/twilio-cli/quickstart):

```
twilio serverless:start
```

5. Open the web page at https://localhost:3000/index.html and enter your phone number to test

ℹ️ Check the developer console and terminal for any errors, make sure you've set your environment variables.

## Deployment and configuration

The full deployment and configuration of this function is a two step process, and requires that you own a Twilio phone number.

### Deployment of the Function

Deploy your function with the following command. Note: you must run this command from inside your project folder. [More details in the docs.](https://www.twilio.com/docs/labs/serverless-toolkit)

With the [Twilio CLI](https://www.twilio.com/docs/twilio-cli/quickstart):

```
twilio serverless:deploy
```

After the deploy is completed you'll be shown some information about your function. Under `Deployment Details` there is a section named `Functions` where you'll find the full callback url to the function. We'll use this as the Voice Callback url on your Twilio phone number.

### Configure the callback url on a phone number

Then we'll need a phone number to attach it to. For this you'll need a phone number sid. You can run the following command to see a summary of the phone numbers you own and their Sids:

```
twilio phone-numbers:list
```

Alternately, from the [Phone Numbers Section of the Twilio Console](https://twilio.com/console/phone-numbers) select the phone number you'd like to configure and then copy its Sid.

Next we use the following command to configure the phone number. Replace `PhoneNumberSid` with the Sid you copied from the console and replace `CallBackUrl` with the url we found in the deployment details of the function deploy response.

```
twilio api:core:incoming-phone-numbers:update \
--sid <PhoneNumberSid> \
--voice-url "<CallBackUrl>"
```
147 changes: 147 additions & 0 deletions personal-voicemail/functions/personal-voicemail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* Personal Voicemail.
*
* See ReadMe for details on
* the configuration options.
*
**/

/*****************************/
/******* configuration *******/

const options = {
phoneNumber: '',
defaultTimeout: 12,
secureRecordingLinks: true,
voiceOpts: {
voice: 'alice',
language: 'en-US'
},
voiceMailMessage: "Hello, I can not answer the phone right now. Please leave a message. Hang up when you're finished.",
reject: [
// To block a caller, add the E164 formatted number here
],
rejectMessage: "You are calling from a restricted number. Goodbye."
}

/***** end configuration *****/
/*****************************/

exports.handler = function(context, event, callback) {
const {
phoneNumber,
defaultTimeout,
secureRecordingLinks,
voiceOpts,
voiceMailMessage,
reject,
rejectMessage
} = options;

const thisFunction = 'https://' + context.DOMAIN_NAME + context.PATH;
const timeout = event.timeout || defaultTimeout;

function shouldReject() {
return reject.length > 0 && event.From && reject.includes(event.From);
}

function rejectInbound() {
let twiml = new Twilio.twiml.VoiceResponse();
if (rejectMessage) {
twiml.say(rejectMessage, voiceOpts);
}
twiml.hangup();
return twiml;
}

function handleInbound() {
const dialParams = {
'action': thisFunction + '?handle-voicemail'
};

if (event.CallerId) {
dialParams.callerId = event.CallerId;
}

if (timeout) {
dialParams.timeout = timeout;
}

const twiml = new Twilio.twiml.VoiceResponse();
twiml.dial(dialParams, phoneNumber);

return twiml;
}

function startVoicemail() {
const twiml = new Twilio.twiml.VoiceResponse();

const prompt = voiceMailMessage.trim();

if (prompt.match(/^(https?:\/\/[^\s]+)$/)) {
twiml.play(prompt);
} else {
twiml.say(prompt, voiceOpts);
}

twiml.record({
action: thisFunction + '?notify-voicemail',
timeout: '10'
});

return twiml;
}

function notifyVoicemail() {
const message = {
to: phoneNumber,
from: event.To,
body: 'New voicemail from ' + event.From
};

if (secureRecordingLinks) {
message.body += ' - https://www.twilio.com/console/voice/logs/calls/' + event.CallSid;
} else {
message.mediaUrl = event.RecordingUrl + '.mp3';
}

context.getTwilioClient().messages.create(message)
.then(msg => callback(null, msg.sid))
.catch(callback);
}

function redirect() {
const twiml = new Twilio.twiml.VoiceResponse();
twiml.redirect(thisFunction);
return twiml;
}

function hangup() {
const twiml = new Twilio.twiml.VoiceResponse();
twiml.hangup();
return twiml;
}

switch (true) {
case event.CallStatus === 'queued':
callback(null, redirect());
break;
case event.CallStatus === 'ringing':
const rejectOrHandle = shouldReject() ?
rejectInbound() :
handleInbound();
callback(null, rejectOrHandle);
break;
case ['canceled', 'busy', 'failed'].includes(event.CallStatus):
case event.CallStatus === 'in-progress' && event.DialCallStatus === 'no-answer':
callback(null, startVoicemail());
break;
case event.CallStatus === 'completed' && event.Digits === 'hangup':
notifyVoicemail();
break;
default:
callback(null, hangup());
}
};

exports.options = options;
4 changes: 4 additions & 0 deletions personal-voicemail/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "personal-voicemail",
"dependencies": {}
}
Loading