Responding to SMS in PHP using AWS Lambda and Bref

July 02, 2019
Written by

lirlC01lUZ-27G-NyFFBrv34hXhjGT9x8FzpwBi8g0eSizEMwtdkfOICi6wok5aYS6eR3Crdg3zO7cJ3hq-iF9Da4etsyTNy48qK_qffrAsklPtWGe3WRpb3u3CiPP9C8WzWTomD

Every new starter at Twilio has to build an application using one of our products, then demo it to receive their fabled Track Jacket. For my application, because WiFi is always a pain at conferences, I wrote a PHP script that sends you the next talks for a given event.

Writing this so it worked locally was relatively straightforward with PHP’s inbuilt web server and ngrok, but when I got up to demo this in front of my peers, I didn’t want to be relying on my laptop to be open, awake and responding to the proxied HTTP requests. This code needs to be sitting somewhere on the internet so that it can respond to messages any time of the day or night, and not just when my laptop was open and connected to the wifi.

Serverless functions are great for this; they allow you to run code on someone else’s computers without having to deploy any infrastructure. Publishing your code can be as simple as running a CLI command, and you only pay for what you use with a generous free tier. Lambda is AWS’s offering in the serverless function space, but sadly it doesn’t support PHP out of the box.

So how can we run PHP applications on AWS Lambda?

Tooling

The Bref PHP open source project makes it relatively painless to deploy our PHP code as a Lambda serverless function. In late 2018, Amazon opened the door to custom runtimes and layers for Lambdas, and Bref takes advantage of this to allow PHP projects to be seamlessly deployed to Lambda. Currently, it uses the AWS SAM CLI library to do this, but integrations with the popular Serverless Framework are coming soon.

Let’s assume we've configured a phone number in the Twilio console to respond to a webhook that we are proxying to our local development environment using ngrok. Our webhook will use the Twilio PHP library to generate some dynamic TwiML that lets us know what time and date an appointment is.

Create a new file called webhook.php in the root of your project, you’ll need to already have installed the Twilio PHP Helper Library using Composer.

<?php

require_once('vendor/autoload.php');

$time = random_int(time(), time() + 2629746);
$appointmentDate = DateTimeImmutable::createFromFormat('U', $time);

$response = new \Twilio\TwiML\MessagingResponse();
$response->message('Your appointment is on ' . $appointmentDate->format(
        'l jS F \a\t g:i A'
    ));

echo $response;

We can wire this up as a webhook to a Twilio phone number that accepts SMS by adding the URL to the configuration for incoming messages. Now, when we send a text to the number, we'll receive a message back that contains the date and time of our appointment.

Screenshot of the Twilio messaging pane of the control panel with the "A Message Comes In" webhook filled in

Let’s use Bref to upload our script to an AWS Lambda so that it’s available to be triggered as a webhook.

We can install Bref using Composer by running the following command in your project root:

composer require bref/bref

Bref ships with a command-line tool that simplifies everyday tasks and we can use this tool to generate the initial configuration files that Bref needs to deploy to Lambda.

When prompted by the CLI, pick an HTTP application (1) so that Bref can configure an HTTP endpoint using Amazon’s API Gateway.

$ vendor/bin/bref init

Animated gif of the results of running the previous command and selecting 1

Bref has created two files in our project:

  • template.yml configures how our application works, and what functions we want to define and how we invoke them
  • index.php is the default “Hello World” PHP script that gets invoked by the default configurations. We can delete this -- we won’t be using it

We’ll need to update the template.yml file to point to the webhook.php file we created earlier. Because an HTTP request invokes this Lambda, it only needs to return text that gets sent as the HTTP response by the API Gateway, so we don’t need to modify the file that we used for local development at all.

Let’s rename the default my-function to something more useful, and update the default values to be more meaningful leaving the rest of the configuration as it is.

Resources:
    TwilioAppointment:
        Type: AWS::Serverless::Function
        Properties:
            FunctionName: 'twilio-appointment'
            Description: 'Sends confirmation of appointment with Twilio'
            CodeUri: .
            Handler: webhook.php

With the application now configured, let's go ahead and deploy to AWS Lambda.

The Bref team have already compiled and deployed a working version of PHP that fits most use-cases as a Lambda layer so we don’t need to worry about doing that. Unless we need custom extensions or configuration the out-of-the-box settings should have us covered.

Before we can deploy, we’ll need to create an Amazon S3 bucket that Bref uses to stage the files that it needs to make the deploy. The bucket name needs to be unique across all of AWS, but we only need to do this once per-project.

aws s3 mb s3://<your-unique-bucket-name>

Next, we are going to package up all the files that are needed to deploy using the SAM CLI:

sam package --output-template-file .stack.yaml --s3-bucket <your-unique-bucket-name>

Let's deploy this function to AWS Lambda and create all of the AWS configurations that allow it to be invoked via HTTP. You’ll need to define a stack name-- the Stack is the bundle of configuration files that are uploaded to AWS CloudFormation to create the infrastructure needed inside AWS, it should be unique to your AWS account.

sam deploy --template-file .stack.yaml --capabilities CAPABILITY_IAM --stack-name <your-stack-name>

Screenshot of the results of running the deploy command

Everything has deployed successfully, but how do we invoke our function? To find the URL, we need to navigate to the CloudFormation section of the AWS Console. Make sure we're viewing the right region if you changed Bref’s configuration or you won’t be able to see your Stack. The URL to invoke the function is under the Outputs tab of the Stack.

Screenshot of the Outputs tab of the AWS Stack control panel

Clicking on the URL opens it in a browser... congratulations, we’ve invoked a PHP script through AWS Lambda!

Now, all that’s left to do is to replace our Twilio number’s SMS webhook to use the new Lambda URL, and we will have a working production system that scales on demand.

Screenshot of an SMS being sent to a Twilio number with appointment details sent as the response

Webhooks can be a very powerful yet relatively simple way to respond to events from our Twilio phone number. SMS, Voice, Whatsapp, Autopilot and more can use webhooks to figure out what they should be doing when something interesting happens. Serverless functions are a great maintenance free solution to host these applications, and Bref makes it easier for us to run PHP on Lambda.

Let me know what you think -- do you run PHP on Lambda another way, or use a different serverless function provider? I’d love to hear more, drop me a Tweet. I can’t wait to see what you build.