-
Notifications
You must be signed in to change notification settings - Fork 329
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
Gipetto
wants to merge
8
commits into
twilio-labs:master
Choose a base branch
from
Gipetto:personal-voicemail
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 5 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
7f81f20
Initial copy of function.
Gipetto f604c93
Initial tests
Gipetto 376599e
Refactor to support testing
Gipetto 01222b9
Final tests
Gipetto 4ff363f
General cleanup
Gipetto 88481b1
Allow variable pathing via context
Gipetto 1eda15e
Update ReadMe
Gipetto 48581d1
Include phonenumber listing directions
Gipetto File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# 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 | ||
|
||
### 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` expects the following parameters as defined in the function source: | ||
|
||
| Parameter | Description | Required | | ||
| :-------- | :---------- | :------- | | ||
| `phoneNumber` | The phone number to which you'd like to forward incoming calls, formatted in E164. | yes | | ||
| `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 | yes | | ||
| `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 | yes | | ||
| `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 | yes | | ||
| `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. | yes | | ||
| `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. | yes | | ||
| `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. | yes | | ||
|
||
|
||
## 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) | ||
|
||
```shell | ||
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. | ||
|
||
## Deploying | ||
|
||
Deploy your functions and assets with either of the following commands. Note: you must run these commands 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 | ||
``` | ||
Gipetto marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 + '/personal-voicemail'; | ||
Gipetto marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"name": "personal-voicemail", | ||
"dependencies": {} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be nice if we would support this via an environment variable as well. I think we use
MY_PHONE_NUMBER
in a couple of the other templates.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have opinions about environment variables. ;)
When developing functions it is very easy for generic vars to clutter the environment, often to the point of clashing with one another. I feel that unless the function actually requires shared state or configuration that it should be kept local to the file. So unless a function requires multiple files to accomplish the task, or hooks in to a larger system's globals, then using environment variables is overkill, over complicating, and unnecessary.
Just in this repo, where all the functions are independent and not related to each other, most functions specify
MY_PHONE_NUMBER
orTWILIO_PHONE_NUMBER
as the environment variable, thus forcing someone who wants to use function-templates with different phone numbers to structurally alter the code to make it function. To the less perceptive it may cause them to change the var and change the behavior of another function.I'd rather see us provide details on how to update a function to use shared environment rather than make them the default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I definitely get your point. I think my only thought about environment variables vs query params is that they are more beginner friendly plus the serverless tooling is already letting people know that they should configure those variables.
Having environment variables with the same name can certainly be both a pro or a con. One of the things we considered when building the tooling was the ability to compose multiple templates together. For example you might start your project with
twilio serverless:init example --template forward-call
which uses theMY_PHONE_NUMBER
. Later you want to be able to also forward your text messages. This would only require you to run inside the same projecttwilio serverless:new sms --forward-message
and after that you'd only have to configure the webhook URL without any other changes.At the same time I can definitely also see the frustration of getting a clashing behavior here.
The other benefit of environment variables is that we could further improve the tooling to ask people if they intend to use the already configured env variable or not. That's a bit harder with query params.
The other thing with query params is that the
event
can be both populated via query parameters and POST body and it can sometimes be confusing which one will be populated by Twilio and which one you have to set yourself.I'm not too opinionated on either solution for your template. If you feel strongly about not offering configuration via env variables that's fine by me too. We just lack more documentation around query params than on environment variables atm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think, too, I just noticed that you wrote
This function uses hard coded vars in the function template and doesn't take any config options via get params. So there's nothing more needed than to update the function before it is deployed. I don't think I could advocate allowing for changing core configuration via GET params - sounds like a huge security issue. So I think this alleviates most of your concerns here around the discoverability of params... they're simply hard coded.
So maybe some clarification in this read me is in order.