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 quickstart-serverless-receive-sms template #412

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 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
260 changes: 154 additions & 106 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
"masked-number",
"never-gonna-give-you-up",
"patient-appointment-management",
"quickstart-serverless-receive-sms",
"segment-event-notification",
"sip-quickstart",
"sms-broadcast",
Expand Down
13 changes: 13 additions & 0 deletions quickstart-serverless-receive-sms/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# description: The Twilio phone number to send SMS from
# format: phone_number
# required: true
TWILIO_PHONE_NUMBER=

# description: Your phone number, to forward test messages to
# format: phone_number
# required: true
MY_PHONE_NUMBER=

# description: The path to the webhook
# configurable: false
TWILIO_SMS_WEBHOOK_URL=/static-response
7 changes: 7 additions & 0 deletions quickstart-serverless-receive-sms/.owners
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dkundel
alisontanu
pthirumurthi

# Insert your Github username here

stern-shawn
9 changes: 9 additions & 0 deletions quickstart-serverless-receive-sms/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Changelog

## [Unreleased]

## [1.0.0]

### Added

- Initial release.
20 changes: 20 additions & 0 deletions quickstart-serverless-receive-sms/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# quickstart-serverless-receive-sms

This template application contains all sample code for a user to follow along the [Receive an inbound SMS](https://www.twilio.com/docs/serverless/functions-assets/quickstart/receive-sms) quickstart guide.

## Pre-requisites

### Environment variables

This project requires some environment variables to be set. A file named `.env` is used to store the values for those environment variables. 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 |
| :------- | :---------- | :------- |
| TWILIO_PHONE_NUMBER | A Twilio phone number |
| MY_PHONE_NUMBER | The phone number to forward test messages to | Yes |

### Function Parameters

Each Function expects the incoming request to be a messaging webhook. The specifics vary between each example, but at most, the parameters that will be used are `From` and `Body`.
112 changes: 112 additions & 0 deletions quickstart-serverless-receive-sms/assets/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Get started with your Twilio Functions!</title>

<link rel="icon" href="https://twilio-labs.github.io/function-templates/static/v1/favicon.ico">
<link rel="stylesheet" href="https://twilio-labs.github.io/function-templates/static/v1/ce-paste-theme.css">
<script src="https://twilio-labs.github.io/function-templates/static/v1/ce-helpers.js" defer></script>
<script>
window.addEventListener('DOMContentLoaded', (_event) => {
inputPrependBaseURL();
});
</script>
</head>
<body>
<div class="page-top">
<header>
<div id="twilio-logo">
<a href="https://www.twilio.com/" target="_blank" rel="noopener">
<svg class="logo" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 60 60">
<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>
</div>
<nav>
<span>Your Twilio application</span>
<aside>
<svg class="icon" role="img" aria-hidden="true" width="100%" height="100%" viewBox="0 0 20 20" aria-labelledby="NewIcon-1577"><path fill="currentColor" fill-rule="evenodd" d="M6.991 7.507c.003-.679 1.021-.675 1.019.004-.012 2.956 1.388 4.41 4.492 4.48.673.016.66 1.021-.013 1.019-2.898-.011-4.327 1.446-4.48 4.506-.033.658-1.01.639-1.018-.02-.03-3.027-1.382-4.49-4.481-4.486-.675 0-.682-1.009-.008-1.019 3.02-.042 4.478-1.452 4.49-4.484zm.505 2.757l-.115.242c-.459.9-1.166 1.558-2.115 1.976l.176.08c.973.465 1.664 1.211 2.083 2.22l.02.05.088-.192c.464-.973 1.173-1.685 2.123-2.124l.039-.018-.118-.05c-.963-.435-1.667-1.117-2.113-2.034l-.068-.15zm10.357-8.12c.174.17.194.434.058.625l-.058.068-1.954 1.905 1.954 1.908a.482.482 0 010 .694.512.512 0 01-.641.056l-.07-.056-1.954-1.908-1.954 1.908a.511.511 0 01-.71 0 .482.482 0 01-.058-.626l.058-.068 1.954-1.908-1.954-1.905a.482.482 0 010-.693.512.512 0 01.64-.057l.07.057 1.954 1.905 1.954-1.905a.511.511 0 01.71 0z"></path></svg>
Live
</aside>
</nav>
</header>
</div>
<main>
<div class="content">
<h1>
<img src="https://twilio-labs.github.io/function-templates/static/v1/success.svg" />
<div>
<p>Welcome!</p>
<p>All code samples are deployed, and ready to test!</p>
</div>
</h1>
<section>
<h2>Get started</h2>
<p>
Every code sample from the <a href="https://www.twilio.com/docs/serverless/functions-assets/quickstart/receive-sms" target="_blank" rel="noopener">Receive an inbound SMS example</a> is now deployed on your behalf, no need to create everything yourself and copy-paste any code :)
</p>
<p>
By default, this app will return the
<a href="https://www.twilio.com/docs/sms/twiml" target="_blank" rel="noopener">TwiML</a>
required to respond "Hello, World!" to incoming SMS messages. Exactly the same as the <a href="https://www.twilio.com/docs/serverless/functions-assets/quickstart/receive-sms?code-sample=code-respond-to-an-inbound-sms&code-language=Node.js&code-sdk-version=default" target="_blank" rel="noopener">first example</a> in the documentation.
</p>
<ol class="steps">
<li>Text any message to your Twilio phone number</li>
<li>You should receive a response saying "Hello World"</li>
</ol>
</section>
<section>
<h2>Experiment with and modify your first example</h2>
<p>Now that you've verified that your Serverless Twilio application works, it's time to make this code your own or proceed to other examples!</p>
<p>The <b>Edit this application</b> button will take you to the Twilio Console, where you can make edits to and re-deploy any of the examples in this guide.</p>
<p>For example: try editing the <code>/static-response</code> code so that the response is something unique to you, other than "Hello, World!". Save the Function, click <b>Deploy All</b> to deploy your updates to the code, and try sending a new text message to your Twilio number to see the modified response.</p>
</section>
<section>
<h2>Next steps</h2>
<p>Once you're satisfied with your changes to <code>/static-response</code>, there are several other code samples to experiment with!</p>
<p>Remember, each code sample is identical to those in the quickstart, and you are encouraged to refer back to <a href="https://www.twilio.com/docs/serverless/functions-assets/quickstart/receive-sms" target="_blank" rel="noopener">the documentation</a> for deeper explanations related to each sample. Each sample also includes helpful comments, to better explain the use for and reasoning behind each Function.</p>
<h3>Modify and test more examples</h3>
<p>To learn from and follow along with the examples in the documentation, follow these steps:</p>
<ol class="steps">
<li>Decide on an example (it's easiest to go in the same order as the documentation does)</li>
<li>Navigate to your <a href="https://www.twilio.com/console/phone-numbers/incoming" target="_blank" rel="noopener">phone number configuration</a>, and click on the phone number that's currently connected to this application.</li>
<li>Scroll down to the configuration titled <b>A Message Comes In</b>. (It should already be configured to handle messages using type <code>Function</code>, and be pointed to your most recently used Function from this application.)</li>
<li>Under <b>Function Path</b>, click the dropdown, and select the path of the example you would like to test out next.</li>
<li>Click <b>Save</b>. Your Phone number will now be connected to the newly selected example.</li>
<li>Send a test message to your phone number, check the response, and modify then re-deploy the code of the responsible Function from the console as necessary.</li>
<li>Repeat these steps until you've tried all the examples, and feel comfortable handling any incoming SMS with Functions!</li>
</ol>
</section>
<section>
<!-- APP_INFO_V2 -->
</section>
<section>
<!-- EDIT_CODE -->
</section>
stern-shawn marked this conversation as resolved.
Show resolved Hide resolved
<section>
<h2>Troubleshooting</h2>
<ul>
<li>
Check your
<a href="https://www.twilio.com/console/phone-numbers/incoming"
target="_blank"
rel="noopener">
phone number configuration</a>
and make sure the Twilio phone number you want for your app has a SMS webhook
configured to point at the following URL
<form>
<label for="twilio-webhook">Webhook URL</label>
<input type="text" id="twilio-webhook" class="function-root" readonly=true value="/static-response">
</form>
</li>
</ul>
</section>
</div>
</main>
<footer>
<span class="statement">We can't wait to see what you build.</span>
</footer>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const axios = require('axios');

// Note that the function must be `async` to enable use of the `await` keyword
exports.handler = async (context, event, callback) => {
// Create a new messaging response object
const twiml = new Twilio.twiml.MessagingResponse();

// You can do anything in a Function, including making async requests for data
const response = await axios
.get('https://dog.ceo/api/breed/shiba/images/random')
.catch((error) => {
/*
* Be sure to handle any async errors, and return them in callback to end
* Function execution if it makes sense for your application logic
*/
console.error(error);
return callback(error);
});

const imageUrl = response.data.message;

/*
* Use any of the Node.js SDK methods, such as `message`, to compose a response
* In this case we're also including the doge image as a media attachment
* Note: access incoming text details such as the from number on `event`
*/
twiml.message(`Hello, ${event.From}! Enjoy this doge!`).media(imageUrl);

/*
* Return the TwiML as the second argument to `callback`
* This will render the response as XML in reply to the webhook request
*/
return callback(null, twiml);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
exports.handler = (context, event, callback) => {
// Create a new messaging response object
const twiml = new Twilio.twiml.MessagingResponse();

// Access the incoming text content from `event.Body`
const incomingMessage = event.Body.toLowerCase();

// Use any of the Node.js SDK methods, such as `message`, to compose a response
if (incomingMessage.includes('hello')) {
twiml.message('Hello, there!');
} else if (incomingMessage.includes('bye')) {
twiml.message('Goodbye!');
} else {
twiml.message('Not sure what you meant! Please say hello or bye!');
}

/*
* Return the TwiML as the second argument to `callback`
* This will render the response as XML in reply to the webhook request
*/
return callback(null, twiml);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
exports.handler = (context, event, callback) => {
// Create a new messaging response object
const twiml = new Twilio.twiml.MessagingResponse();
/*
* Use any of the Node.js SDK methods, such as `message`, to compose a response
* Access incoming text information like the from number and contents off of `event`
* Access environment variables and other runtime data from `context`
* Note: providing the `to` parameter like so will forward this text instead of responding to the sender
*/
twiml.message(`From: ${event.From}. Body: ${event.Body}`, {
to: context.MY_PHONE_NUMBER,
});
/*
* Return the TwiML as the second argument to `callback`
* This will render the response as XML in reply to the webhook request
*/
return callback(null, twiml);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const MY_PHONE_NUMBER = '+15095550100';

exports.handler = (context, event, callback) => {
// Create a new messaging response object
const twiml = new Twilio.twiml.MessagingResponse();
/*
* Use any of the Node.js SDK methods, such as `message`, to compose a response
* Access incoming text information like the from number and contents off of `event`
* Note: providing the `to` parameter like so will forward this text instead of responding to the sender
*/
twiml.message(`From: ${event.From}. Body: ${event.Body}`, {
to: MY_PHONE_NUMBER,
});
/*
* Return the TwiML as the second argument to `callback`
* This will render the response as XML in reply to the webhook request
*/
return callback(null, twiml);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
exports.handler = (context, event, callback) => {
// Create a new messaging response object
const twiml = new Twilio.twiml.MessagingResponse();
// Use any of the Node.js SDK methods, such as `message`, to compose a response
twiml.message('Hello, World!');
/*
* Return the TwiML as the second argument to `callback`
* This will render the response as XML in reply to the webhook request
*/
return callback(null, twiml);
};
11 changes: 11 additions & 0 deletions quickstart-serverless-receive-sms/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "quickstart-serverless-receive-sms",
"version": "1.0.0",
"private": true,
"dependencies": {
"@twilio-labs/runtime-helpers": "^0.1.2"
},
"workspaces": [
"quickstart-serverless-receive-sms"
]
}
60 changes: 60 additions & 0 deletions quickstart-serverless-receive-sms/tests/async-mms-resonse.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const helpers = require('../../test/test-helper');
const asyncMmsResponse =
require('../functions/async-mms-response.protected').handler;
const Twilio = require('twilio');
const axios = require('axios');

jest.mock('axios');

const context = {};
const event = {
From: 'IncomingNumber',
};

beforeAll(() => {
helpers.setup(context);
});

afterAll(() => {
helpers.teardown();
});

test('returns a MessagingResponse', (done) => {
axios.get.mockResolvedValueOnce({ data: { message: 'www.doggo.com' } });

const callback = (_err, result) => {
expect(result).toBeInstanceOf(Twilio.twiml.MessagingResponse);
done();
};

asyncMmsResponse(context, event, callback);
});

test('replies with a message and includes media', (done) => {
axios.get.mockResolvedValueOnce({ data: { message: 'www.doggo.com' } });

const callback = (_err, result) => {
expect(result.toString()).toMatch(`Hello, ${event.From}! Enjoy this doge!`);
expect(result.toString()).toMatch(`<Media>www.doggo.com</Media>`);
done();
};

asyncMmsResponse(context, event, callback);
});

// This test behaves in Node v14 but fails unexpectedly in Node v16 🤷‍♂️
test.skip('handles and logs network errors', (done) => {
const error = new Error('doggos down');
axios.get.mockRejectedValueOnce(error);
console.error = jest.fn();

const callback = (err, result) => {
expect(err).toBeDefined();
expect(result).toBeUndefined();
expect(console.error).toHaveBeenCalledTimes(1);
expect(console.error).toHaveBeenCalledWith(error);
done();
};

asyncMmsResponse(context, event, callback);
});
Loading