Add a small example with a custom Twilio component
ADD
twilio-ts-component/Pulumi.yaml
+name: twilio-ts-component
+description: A small example that builds a Pulumi component for Twilio Programable SMS
+runtime: nodejs

ADD
twilio-ts-component/index.ts
+import * as pulumi from "@pulumi/pulumi";
+import * as twilo from "./twilio"
+
+const config = new pulumi.Config(pulumi.getProject());
+const phoneNumberSid = config.require("phoneNumberSid");
+
+const handler = new twilo.IncomingPhoneNumber("twilio-example", {
+ phoneNumberSid: phoneNumberSid,
+ handler: async (p) => {
+ return {
+ statusCode: 200,
+ headers: {
+ "Content-Type": "text/plain"
+ },
+ body: `Made with \u2764 and Pulumi.`
+ }
+ }
+});
+
+// We export the SMS URL, for debugging, you can post messages to it with curl to test out your handler without
+// having to send an SMS. For example:
+//
+// $ curl -X POST -d "From=+12065555555" -d "Body=Hello!" $(pulumi stack output smsUrl)
+//
+// There are many additional properties you can provide which will be decoded and presented to your handler,
+// see: https://www.twilio.com/docs/sms/twiml#request-parameters
+export let smsUrl = handler.smsUrl;
\ No newline at end of file

ADD
twilio-ts-component/package.json
+{
+ "name": "twilio-ts-component",
+ "main": "bin/index.js",
+ "typings": "bin/index.d.ts",
+ "scripts": {
+ "build": "tsc"
+ },
+ "devDependencies": {
+ "typescript": "^2.7.2",
+ "@types/node": "latest"
+ },
+ "dependencies": {
+ "@pulumi/pulumi": "^0.12.3",
+ "@pulumi/aws-serverless": "dev",
+ "twilio": "latest"
+ }
+}

ADD
twilio-ts-component/tsconfig.json
+{
+ "compilerOptions": {
+ "outDir": "bin",
+ "target": "es6",
+ "lib": [
+ "es6"
+ ],
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "declaration": true,
+ "sourceMap": true,
+ "stripInternal": true,
+ "experimentalDecorators": true,
+ "pretty": true,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitAny": true,
+ "noImplicitReturns": true,
+ "forceConsistentCasingInFileNames": true,
+ "strictNullChecks": true
+ },
+ "files": [
+ "index.ts"
+ ]
+}

ADD
twilio-ts-component/twilio.ts
+import * as pulumi from "@pulumi/pulumi";
+import * as serverless from "@pulumi/aws-serverless";
+import * as url from "url";
+import { Callback } from "@pulumi/aws-serverless/function";
+import { APIArgs } from "@pulumi/aws-serverless/api";
+
+const config = new pulumi.Config("twilio");
+const accountSid = config.require("accountSid");
+const authToken = config.require("authToken");
+
+export class IncomingPhoneNumber extends pulumi.ComponentResource {
+ public /*out*/ readonly smsUrl: pulumi.Output<string>;
+
+ constructor(name: string, args: IncomingPhoneNumberArgs, opts? : pulumi.ResourceOptions) {
+ super("twilio:rest:IncomingPhoneNumber", name, {}, opts);
+
+ const apiArgs : APIArgs = {
+ routes: [{
+ path: "/sms",
+ method: "POST",
+ handler: (e, ctx, cb) => {
+ // Twilio passes information to POST requests as application/x-www-form-urlencoded, so we decode
+ // the body of the request to get at it.
+ const qs = require("querystring");
+ const params = qs.parse(e.isBase64Encoded ? Buffer.from(e.body, "base64").toString() : e.body);
+
+ // Loop over any provided media and add it to our array.
+ const allMedia: MediaInfo[] = []
+ for (let i = 0; i < params.NumMedia; i++) {
+ allMedia.push({
+ ContentType: <string>params[`MediaContentType${i}`],
+ Url: <string>params[`MediaContentUrl${i}`]
+ })
+ }
+
+ // Copy the payload of the request into our representation.
+ const payload: SmsPayload = {
+ MessageSid: params.MessageSid,
+ AcountSid: params.AccountSid,
+ MessagingServiceSid: params.MessagingServiceSid,
+ From: params.From,
+ To: params.To,
+ Body: params.Body,
+ Media: allMedia,
+ FromLocation: {
+ City: params.FromCity,
+ State: params.FromState,
+ Zip: params.FromZip,
+ Country: params.FromCountry,
+ },
+ ToLocation: {
+ City: params.ToCity,
+ State: params.ToState,
+ Zip: params.ToZip,
+ Country: params.ToCountry,
+ }
+ }
+
+ // Delegate to the user provided handler.
+ return args.handler(payload, ctx, cb);
+ }
+ }]
+ };
+
+ const api = new serverless.apigateway.API(`${name}-api`, apiArgs, { parent: this });
+ this.smsUrl = api.url.apply(url => `${url}sms`);
+
+ // Use the twilio SDK to update the handler for the SMS webhook to what we just created.
+ const twilio = require("twilio")
+ const client = new twilio(accountSid, authToken);
+
+ this.smsUrl.apply(url =>{
+ client.incomingPhoneNumbers(args.phoneNumberSid).update({
+ smsMethod: "POST",
+ smsUrl: `${url}`
+ }).done();
+ });
+
+ // Register the smsUrl as an output of the component itself.
+ super.registerOutputs({
+ smsUrl: this.smsUrl
+ });
+ }
+}
+
+export interface IncomingPhoneNumberArgs {
+ phoneNumberSid: string,
+ handler: Callback<SmsPayload, serverless.apigateway.Response>
+}
+
+// See https://www.twilio.com/docs/sms/twiml#request-parameters for more information about
+// what each parameter means.
+export interface SmsPayload {
+ MessageSid: string,
+ AcountSid: string,
+ MessagingServiceSid: string,
+ From: string,
+ To: string,
+ Body: string,
+ Media: MediaInfo[];
+ FromLocation: Location,
+ ToLocation: Location,
+}
+
+// Twilio attempts to look up this information and provide it, but it may not always be present.
+export interface Location {
+ City?: string,
+ State?: string,
+ Zip?: string,
+ Country?: string,
+}
+
+export interface MediaInfo {
+ ContentType: string,
+ Url: string,
+}
\ No newline at end of file

Add a small example with a custom Twilio component