-
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 voice-queue template #149
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# description: The URL for the hold music you want to play. The default is classic.mp3. | ||
# format: url | ||
# required: true | ||
HOLD_MUSIC_URL=https://demo.twilio.com/docs/classic.mp3 | ||
|
||
# description: Your personal number to forward the inbound caller to. | ||
# format: phone_number | ||
# required: true | ||
YOUR_PHONE_NUMBER=+12223334444 | ||
|
||
# description: The Twilio number used to forward the call. | ||
# format: phone_number | ||
# required: true | ||
TWILIO_PHONE_NUMBER=+15555555555 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# Voice Queue | ||
|
||
A simple call queuing system. Place the inbound caller into a queue with hold music while waiting for the intended recipient to answer. | ||
|
||
## Pre-requisites | ||
|
||
### Environment variables | ||
|
||
This project requires some environment variables to be set. To keep your tokens and secrets secure, make sure to not commit the `.env` file in git. When setting up the project with `twilio serverless:init ...` the Twilio CLI will create a `.gitignore` file that excludes `.env` from the version history. | ||
|
||
In your `.env` file, set the following values: | ||
|
||
| Variable | Description | Required | | ||
| :-------------------- | :--------------------------------------------------- | :------- | | ||
| `HOLD_MUSIC_URL` | The URL for the hold music you want to play. | true | | ||
| `YOUR_PHONE_NUMBER` | Your personal number to forward the inbound call to. | true | | ||
| `TWILIO_PHONE_NUMBER` | The Twilio number used to forward the call. | true | | ||
|
||
### Function Parameters | ||
|
||
`/dial-queue` expects the following parameters: | ||
|
||
| Parameter | Description | Required | | ||
| :-------- | :-------------------------------------------------------------- | :------- | | ||
| `CallSid` | Use the inbound CallSid as the unique identifier for the Queue. | true | | ||
| `From` | Whisper who is calling to the intended recipient. | true | | ||
|
||
## 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=voice-queue && cd voice-queue | ||
``` | ||
|
||
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 | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Get started with your Twilio Functions!</title> | ||
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic"> | ||
<link rel="stylesheet" href="https://unpkg.com/normalize.css/normalize.css"> | ||
<link rel="stylesheet" href="https://unpkg.com/milligram/dist/milligram.min.css"> | ||
<style> | ||
body { | ||
padding: 20px; | ||
display: flex; | ||
flex-direction: column; | ||
min-height: 100vh; | ||
} | ||
|
||
div[role="main"] { | ||
flex: 1; | ||
} | ||
|
||
footer { | ||
text-align: center; | ||
} | ||
|
||
footer p { | ||
margin-bottom: 0; | ||
} | ||
|
||
#twilio-logo { | ||
width: 50px; | ||
height: 50px; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<header> | ||
<h1>Welcome to your new Twilio App</h1> | ||
</header> | ||
<div role="main"> | ||
<section> | ||
<h3>Get started with your app</h3> | ||
<p>Now that your code is deployed, here are the last steps you need to do to finish your app.</p> | ||
<ol> | ||
<li>Have anyone call your Twilio app.</li> | ||
<li>The caller will hear music while they are on hold.</li> | ||
<li>An outbound call will dial your personal number, and connect you to the initiated caller.</li> | ||
</ol> | ||
</section> | ||
<section> | ||
<h3>Troubleshooting</h3> | ||
<ul> | ||
<li>Make sure the Twilio phone number you want your app has a voice webhook configured to point at | ||
</li> | ||
<p> | ||
<pre><code><span class="function-root"></span>/enqueue</code></pre> | ||
</p> | ||
<li>Check the | ||
<code>README.md</code> | ||
of your project</li> | ||
</ul> | ||
</section> | ||
</div> | ||
<footer> | ||
<p> | ||
<a href="https://www.twilio.com/"> | ||
<svg id="twilio-logo" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 60 60"> | ||
<defs> | ||
<style> | ||
.cls-1 { | ||
fill: #f22f46; | ||
} | ||
</style> | ||
</defs> | ||
<title>Twilio Logo</title><path class="cls-1" d="M30,15A15,15,0,1,0,45,30,15,15,0,0,0,30,15Zm0,26A11,11,0,1,1,41,30,11,11,0,0,1,30,41Zm6.8-14.7a3.1,3.1,0,1,1-3.1-3.1A3.12,3.12,0,0,1,36.8,26.3Zm0,7.4a3.1,3.1,0,1,1-3.1-3.1A3.12,3.12,0,0,1,36.8,33.7Zm-7.4,0a3.1,3.1,0,1,1-3.1-3.1A3.12,3.12,0,0,1,29.4,33.7Zm0-7.4a3.1,3.1,0,1,1-3.1-3.1A3.12,3.12,0,0,1,29.4,26.3Z"/></svg> | ||
</a> | ||
</p> | ||
<p> | ||
<a href="https://www.twilio.com/code-exchange">Learn about other things you can build with Twilio</a> | ||
</p> | ||
</footer> | ||
|
||
<script> | ||
// This code will replace the content of any <span class="function-root"></span> with the base path of the URL | ||
const baseUrl = new URL(location.href); | ||
baseUrl.pathname = baseUrl | ||
.pathname | ||
.replace(/\/index\.html$/, ''); | ||
delete baseUrl.hash; | ||
delete baseUrl.search; | ||
const fullUrl = baseUrl | ||
.href | ||
.substr(0, baseUrl.href.length - 1); | ||
const functionRoots = document.querySelectorAll('span.function-root'); | ||
|
||
functionRoots.forEach(element => { | ||
element.innerText = fullUrl | ||
}) | ||
</script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
exports.handler = function (context, event, callback) { | ||
const fromSid = event.fromCallSid; | ||
// If no fromSid is set, error out. | ||
if (!fromSid) { | ||
return callback({ | ||
status: 500, | ||
message: 'No CallSid found. Inbound voice webhook should be set to /enqueue' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Returning a string here should be sufficient to trigger a 500 error from the Function. Runtime, as far as I know, won't do anything special with this as an object. |
||
}, null); | ||
} | ||
|
||
// Split the phone number out so that <Say> reads every number when identifying the caller. | ||
const from = event.from; | ||
const fromFormatted = from.split('').join(' '); | ||
|
||
// Announce who is calling. | ||
const response = new Twilio.twiml.VoiceResponse(); | ||
response.say(`You have an incoming call from ${fromFormatted}`); | ||
// Dial into the queue where the initiated caller is waiting. | ||
response.dial().queue(fromSid); | ||
|
||
callback(null, response); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
exports.handler = function (context, event, callback) { | ||
const { CallSid, From } = event; | ||
|
||
if (From) { | ||
const client = context.getTwilioClient(); | ||
const path = `https://${context.DOMAIN_NAME}`; | ||
// Make an outbound call to your phone number to connect the two legs. | ||
// Use the inbound CallSid as the unique identifier for the queue. | ||
client.calls.create({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a chance of a race condition here between creating the call and the function ending, as we don't wait for this asynchronous call to complete before calling back? Also, should the function just fail if there's no So could this all be? exports.handler = async function (context, event, callback) {
const { CallSid, From } = event;
if (!From) {
callback("Calls must have a `From`.");
} else {
const client = context.getTwilioClient();
const path = `https://${context.DOMAIN_NAME}`;
// Make an outbound call to your phone number to connect the two legs.
// Use the inbound CallSid as the unique identifier for the queue.
try {
await client.calls.create({
to: context.YOUR_PHONE_NUMBER,
from: context.TWILIO_PHONE_NUMBER,
url: `${path}/dial-queue?fromCallSid=${CallSid}&from=${From}`
});
// Enqueue the inbound caller.
// waitUrl points to an endpoint to <Play> the mp3 set in the env variables.
const response = new Twilio.twiml.VoiceResponse();
response.enqueue({
waitUrl: '/hold-music'
}, CallSid);
callback(null, response);
} catch (error) {
callback(error);
}
}
}; |
||
to: context.YOUR_PHONE_NUMBER, | ||
from: context.TWILIO_PHONE_NUMBER, | ||
url: `${path}/dial-queue?fromCallSid=${CallSid}&from=${From}` | ||
}); | ||
} | ||
// Enqueue the inbound caller. | ||
// waitUrl points to an endpoint to <Play> the mp3 set in the env variables. | ||
const response = new Twilio.twiml.VoiceResponse(); | ||
response.enqueue({ | ||
waitUrl: '/hold-music' | ||
}, CallSid); | ||
|
||
callback(null, response); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
exports.handler = function (context, event, callback) { | ||
const response = new Twilio.twiml.VoiceResponse(); | ||
// HOLD_MUSIC_URL points to the audio file set in your environment variables. | ||
response.play(context.HOLD_MUSIC_URL); | ||
callback(null, response); | ||
}; |
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.
Should name this project
voice-queue
notexample
(or change tocd
intoexample
).