diff --git a/package-lock.json b/package-lock.json index 303ba592..fb6b7948 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,6 +74,7 @@ "voice-javascript-sdk", "voicemail", "verify-sna", + "reminder-message", "flex-dialpad" ], "devDependencies": { @@ -13066,6 +13067,10 @@ "node": ">=6.5.0" } }, + "node_modules/reminder-message": { + "resolved": "reminder-message", + "link": true + }, "node_modules/remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -15730,6 +15735,13 @@ "validator": "^13.6.0" } }, + "reminder-message": { + "version": "1.0.0", + "dependencies": { + "@twilio-labs/runtime-helpers": "^0.1.2", + "twilio": "^3.83.1" + } + }, "segment-event-notification": { "version": "1.0.0" }, @@ -25665,6 +25677,13 @@ "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, + "reminder-message": { + "version": "file:reminder-message", + "requires": { + "@twilio-labs/runtime-helpers": "^0.1.2", + "twilio": "3.83.1" + } + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", diff --git a/package.json b/package.json index 7663d676..9b9ebcd4 100644 --- a/package.json +++ b/package.json @@ -138,6 +138,7 @@ "voice-javascript-sdk", "voicemail", "verify-sna", + "reminder-message", "flex-dialpad" ] } diff --git a/reminder-message/.env.example b/reminder-message/.env.example new file mode 100644 index 00000000..dd23e915 --- /dev/null +++ b/reminder-message/.env.example @@ -0,0 +1,14 @@ +# description: SID of a Twilio Messaging Service that will be used for your reminder email. +# format: sid +# link: https://console.twilio.com/us1/develop/sms/services +# required: true +MESSAGING_SERVICE_SID= + +# description: Delay of how many minutes it should take after the incoming message to sent out a reminder. Minimum: 15 minutes +# format: number +# required: false +DELAY_IN_MINUTES= + +# description: The path to the webhook +# configurable: false +TWILIO_SMS_WEBHOOK_URL=/respond \ No newline at end of file diff --git a/reminder-message/.owners b/reminder-message/.owners new file mode 100644 index 00000000..e2c1252c --- /dev/null +++ b/reminder-message/.owners @@ -0,0 +1,4 @@ +dkundel +alisontanu +pthirumurthi +# Insert your Github username here diff --git a/reminder-message/CHANGELOG.md b/reminder-message/CHANGELOG.md new file mode 100644 index 00000000..3982d461 --- /dev/null +++ b/reminder-message/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## [Unreleased] + +## [1.0.0] +### Added +- Initial release. + diff --git a/reminder-message/README.md b/reminder-message/README.md new file mode 100644 index 00000000..d6b8478b --- /dev/null +++ b/reminder-message/README.md @@ -0,0 +1,59 @@ +# reminder-message + +Schedule a reminder message to be sent a specified time after the initial message + +## 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 | +| :------- | :---------- | :------- | +| `MESSAGING_SERVICE_SID` | The SID of a valid Messaging Service that is used to send out the scheduled message | Yes | +| `DELAY_IN_MINUTES` | Configurable time to delay the delivery of the reminder message. Defaults to the minimum of 15 minutes | No | + +### Function Parameters + +`/respond` is protected and requires a valid Twilio signature as well as the following parameters: + +| Parameter | Description | Required | +| :-------- | :---------- | :------- | +| `From` | The phone number the incoming message was sent from. Gets automatically passed by Twilio | 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=reminder-message && cd example +``` + +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 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 +``` diff --git a/reminder-message/assets/index.html b/reminder-message/assets/index.html new file mode 100644 index 00000000..9fcfbe43 --- /dev/null +++ b/reminder-message/assets/index.html @@ -0,0 +1,105 @@ + + + + + + + + Get started with your Twilio Functions! + + + + + + + + +
+
+ + +
+
+
+
+

+ +
+

Welcome!

+

Your live application with Twilio is ready to use!

+
+

+
+

Get started with your application

+

+ Follow these steps to try out your new app: +

+

+ This app is configured to handle incoming messages to your Twilio phone number, respond with a + default message or if you send "Y" to the number it will schedule reminder message to be sent out in + 15 minutes (or a different delay if you used another delay). +

+
    +
  1. Text any message to your Twilio phone number.
  2. +
  3. You should receive a welcoming response.
  4. +
  5. Text "Y" to your number.
  6. +
  7. You should receive a confirmation message and another message will be scheduled.
  8. +
  9. After 15 minutes (or whatever delay you configured) you'll receive another message to your + number.
  10. +
+
+
+ +
+
+

Troubleshooting

+ +
+
+
+ + + + \ No newline at end of file diff --git a/reminder-message/functions/respond.protected.js b/reminder-message/functions/respond.protected.js new file mode 100644 index 00000000..ee5a85ab --- /dev/null +++ b/reminder-message/functions/respond.protected.js @@ -0,0 +1,34 @@ +exports.handler = async function (context, event, callback) { + const twiml = new Twilio.twiml.MessagingResponse(); + + if (typeof event.Body === 'string' && event.Body.toLowerCase() === 'y') { + let delay = parseInt(context.DELAY_IN_MINUTES, 10); + if (isNaN(delay)) { + delay = 15; + } + + const client = context.getTwilioClient(); + try { + await client.messages.create({ + from: context.MESSAGING_SERVICE_SID, + to: event.From, // sending to the phone number that sent us a message + body: "Here's the reminder for your appointment. Have a great day!", + scheduleType: 'fixed', + sendAt: new Date(Date.now() + delay * 60 * 1000 + 10000), // add the delay to the current time and add a 10 second buffer to avoid hitting the minimum delay of 15 minutes + }); + twiml.message( + `Thank you! We will send you a reminder in ${delay} minutes` + ); + } catch (err) { + console.error(err); + twiml.message( + 'Something went wrong with scheduling the message. Please check your Twilio logs.' + ); + } + } else { + twiml.message( + 'Hello there! Please send "Y" to confirm your appointment and we will send you a reminder.' + ); + } + return callback(null, twiml); +}; diff --git a/reminder-message/package.json b/reminder-message/package.json new file mode 100644 index 00000000..a371d1e2 --- /dev/null +++ b/reminder-message/package.json @@ -0,0 +1,9 @@ +{ + "name": "reminder-message", + "version": "1.0.0", + "private": true, + "dependencies": { + "@twilio-labs/runtime-helpers": "^0.1.2", + "twilio": "^3.83.1" + } +} diff --git a/reminder-message/tests/respond.test.js b/reminder-message/tests/respond.test.js new file mode 100644 index 00000000..c2d22b53 --- /dev/null +++ b/reminder-message/tests/respond.test.js @@ -0,0 +1,83 @@ +const helpers = require('../../test/test-helper'); +const { handler } = require('../functions/respond.protected'); +const Twilio = require('twilio'); + +const mockTwilioClient = { + messages: { + create: jest.fn(() => { + return Promise.resolve({ + sid: 'my-new-sid', + }); + }), + }, +}; + +const context = { + getTwilioClient: () => mockTwilioClient, +}; +const event = {}; + +beforeAll(() => { + helpers.setup(context); +}); + +afterAll(() => { + helpers.teardown(); +}); + +test('returns a MessagingResponse', (done) => { + const callback = (_err, result) => { + expect(result).toBeInstanceOf(Twilio.twiml.MessagingResponse); + done(); + }; + + handler(context, event, callback); +}); + +test('sends default message', (done) => { + const callback = (_err, result) => { + expect(result.toString()).toMatch( + 'Hello there! Please send "Y" to confirm your appointment and we will send you a reminder.' + ); + done(); + }; + + handler(context, event, callback); +}); + +test('sends scheduled message confirmation with 5 minutes default', (done) => { + const callback = (_err, result) => { + expect(result.toString()).toMatch( + 'Thank you! We will send you a reminder in 15 minutes' + ); + done(); + }; + + handler(context, { ...event, Body: 'Y' }, callback); +}); + +test('sends confirmation with configured delay', (done) => { + const callback = (_err, result) => { + expect(result.toString()).toMatch( + 'Thank you! We will send you a reminder in 30 minutes' + ); + done(); + }; + + handler( + { ...context, DELAY_IN_MINUTES: '30' }, + { ...event, Body: 'Y' }, + callback + ); +}); + +test('handles lower case y as confirmation', (done) => { + const callback = (_err, result) => { + expect(result.toString()).toMatch( + 'Thank you! We will send you a reminder in 15 minutes' + ); + done(); + }; + + handler(context, { ...event, Body: 'y' }, callback); +}); diff --git a/templates.json b/templates.json index b294ae76..cf211306 100644 --- a/templates.json +++ b/templates.json @@ -339,6 +339,11 @@ "id": "transfers", "name": "Transfers", "description": "Transfers a call to another number" + }, + { + "id": "reminder-message", + "name": "Send scheduled reminder messages", + "description": "Schedule a reminder message to be sent a specified time after the initial message" } ] }