Send an SMS Message During a Phone Call with PHP and Slim

August 21, 2018
Written by
Kat King
Twilion
Reviewed by
Paul Kamp
Twilion
Maylon Pedroso
Contributor
Opinions expressed by Twilio contributors are their own
Jonatas deOliveira
Contributor
Opinions expressed by Twilio contributors are their own

sms-during-call-php

In this tutorial, we’ll show you how to use Twilio's Programmable Voice and SMS to send a text message during a phone call all using PHP.

We’ll write a small PHP web application that:

  1. Accepts an incoming phone call
  2. Says something to your caller
  3. Sends the caller a text message

The code we’re going to write uses the Slim web framework and Twilio’s PHP helper library.

Ready? Let’s get started!

Sign up for a Twilio account and get a phone number

If you have a Twilio account and Twilio phone number with SMS and Voice capabilities, you’re all set here! Feel free to jump to the next step.

Before you can receive phone calls and send messages, you’ll need to sign up for a Twilio account and buy a Twilio phone number.

If you don’t currently own a Twilio phone number with both SMS and Voice capabilities, you’ll need to buy one. After navigating to the Buy a Number page, check the "SMS" and "Voice" boxes and click "Search":

Buy A Number Voice SMS

 

You’ll then see a list of available phone numbers that meet your criteria. Find a number that suits you and click "Buy" to add it to your account.

Create a Slim web app that accepts incoming calls

We’ll be building a small Slim web application that accepts incoming calls and sends the caller an SMS. Let’s start by building out the code that receives the call and says something to the caller. We'll be using Composer to manage our dependencies.

Editor: this is a migrated tutorial. You can clone the code from https://github.com/TwilioDevEd/send-sms-during-inbound-calls-php/

Set up your development environment

To download and run the code used in this tutorial, you can clone this GitHub repository and follow the setup instructions in the README.

If you’d prefer to build this app from scratch, start by creating a new directory named twilio_calls. Within that directory, install the necessary dependencies using Composer (composer install).

If you don’t already have the Twilio PHP helper library installed, you can do so with the following command:

composer require twilio/sdk

Don't want to use Composer? Find other ways to install the Twilio PHP Helper library in the PHP quickstart for Programmable Voice.

You will also need to install Slim. If using Composer, this looks like:

composer require slim/slim

Handle incoming calls

Now that our environment is all set up we can create a file named index.php. We’ll create a small /answer route that can say something to someone who calls our new Twilio phone number.

<?php
namespace TwilioDevEd;

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Factory\AppFactory;
use Twilio\TwiML\VoiceResponse;
use Twilio\Rest\Client;
use Twilio\Exceptions\RestException;

class App
{
  /**
   * Stores an instance of the Slim application.
   *
   * @var \Slim\App
   */
  private $app;

  public function __construct() {
    $app = AppFactory::create();
    $app->post('/answer', function (Request $request, Response $response, array $args) {
      $parsedBody = $request->getParsedBody();
      $caller = $parsedBody['From'];
      $twilioNumber = $parsedBody['To'];
      $this->sendSms($caller, $twilioNumber);

      $twilioResponse = new VoiceResponse();
      $twilioResponse->say('Thanks for calling! We just sent you a text with a clue.', ['voice' => 'alice']);

      $response->getBody()->write(strval($twilioResponse));
      return $response;
    });

    $app->get('/', function (Request $request, Response $response, array $args) {
      $response->getBody()->write("Please configure your Twilio number to use the /answer endpoint");
      return $response;
    });


    $this->app = $app;
  }

  public function sendSms(string $toNumber, string $fromNumber, Client $client = null): bool {
      $accountSid = getenv('ACCOUNT_SID');
      $authToken = getenv('AUTH_TOKEN');
      $client = $client ?? new Client($accountSid, $authToken);

      try {
          $client->messages->create(
              $toNumber,
              [
                  'from' => $fromNumber,
                  'body' => "There's always money in the banana stand.",
              ]
          );
      } catch (RestException $e) {
          if ($e->getStatusCode() == 21614) {
              echo "Uh oh, looks like this caller can't receive SMS messages.";
              return false;
          }
      }
      return true;
  }


  /**
   * Get an instance of the application.
   *
   * @return \Slim\App
   */
  public function get()
  {
      return $this->app;
  }
}

In the code sample above, we leveraged Twilio's PHP library to create a Twilio\TwiML\VoiceResponse that says some text to a caller. We can ignore the blurred lines for now, those will come into play later when we're ready to create an SMS from this call.

You can now run this Slim application by spinning up a server:

php -S localhost:8080

You can check that your app is running by going to http://127.0.0.1:8080/answer in your browser. You should see some text that says "Thanks for calling! We just sent you a text with a clue."

There's one problem, however: your app probably can't be seen on the wider internet, only locally. How do we tell Twilio to use this response when someone calls our Twilio phone number?

Allow Twilio to talk to your Slim application

For Twilio to know how to handle incoming calls to your phone number, you’ll need to give this local application a publicly accessible URL. We recommend using ngrok.

If you’re new to ngrok, you can find more information here about how it works and why we recommend using it when developing locally.

Once you’ve downloaded ngrok, make sure your Slim application is running. Then, open a new terminal window and start ngrok:

./ngrok http 8080 -host-header="localhost:8080"

If your local server is running on a port other than 8080, replace '8080' in the command above with the correct port number.

You should see some output that tells you your public ngrok URL.

ngrok output public url

Now you can configure your Twilio phone number to use this app when someone calls you:

  1. Log in to twilio.com and go to the console's Phone Numbers page.
  2. Click on your voice-enabled phone number.
  3. Find the "Voice & Fax" section. Make sure the "Accept Incoming" selection is set to "Voice Calls." The default "Configure With" selection is what you’ll need: "Webhooks/TwiML...".
  4. In the "A Call Comes In" section, select "Webhook" and paste in the URL you want to use, appending your /answer route:

Configure your voice webhook

 

Save your changes. Now you're ready to test it out!

Call the Twilio phone number that you just configured. You should hear your message and the call will end.

Great! Next, we'll get some information about the caller so we can send them a follow-up SMS.

Get your caller's phone number from Twilio's request

When someone dials your Twilio phone number, Twilio sends some extra data in its request to your application.

While Twilio sends a lot of data with each inbound call, the two pieces of information we need are:

  1. Our incoming caller’s phone number: From
  2. Our Twilio phone number:   To

While we won’t be using them in this app, many values are included in Twilio’s request to our application. Check out the full list of parameters Twilio sends with every request.

To send our caller an SMS, we'll need to access that To and From information. Update your /answer route to include the following:

$parsedBody = $request->getParsedBody();
$caller = $parsedBody['From'];
$twilioNumber = $parsedBody['To'];

The code should now look like this:

<?php
namespace TwilioDevEd;

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Factory\AppFactory;
use Twilio\TwiML\VoiceResponse;
use Twilio\Rest\Client;
use Twilio\Exceptions\RestException;

class App
{
  /**
   * Stores an instance of the Slim application.
   *
   * @var \Slim\App
   */
  private $app;

  public function __construct() {
    $app = AppFactory::create();
    $app->post('/answer', function (Request $request, Response $response, array $args) {
      $parsedBody = $request->getParsedBody();
      $caller = $parsedBody['From'];
      $twilioNumber = $parsedBody['To'];
      $this->sendSms($caller, $twilioNumber);

      $twilioResponse = new VoiceResponse();
      $twilioResponse->say('Thanks for calling! We just sent you a text with a clue.', ['voice' => 'alice']);

      $response->getBody()->write(strval($twilioResponse));
      return $response;
    });

    $app->get('/', function (Request $request, Response $response, array $args) {
      $response->getBody()->write("Please configure your Twilio number to use the /answer endpoint");
      return $response;
    });


    $this->app = $app;
  }

  public function sendSms(string $toNumber, string $fromNumber, Client $client = null): bool {
      $accountSid = getenv('ACCOUNT_SID');
      $authToken = getenv('AUTH_TOKEN');
      $client = $client ?? new Client($accountSid, $authToken);

      try {
          $client->messages->create(
              $toNumber,
              [
                  'from' => $fromNumber,
                  'body' => "There's always money in the banana stand.",
              ]
          );
      } catch (RestException $e) {
          if ($e->getStatusCode() == 21614) {
              echo "Uh oh, looks like this caller can't receive SMS messages.";
              return false;
          }
      }
      return true;
  }


  /**
   * Get an instance of the application.
   *
   * @return \Slim\App
   */
  public function get()
  {
      return $this->app;
  }
}

Getting the Twilio phone number from the request allows us to connect this app to multiple phone numbers and route our responses appropriately.

Now that we have our Twilio phone number and our caller’s phone number, we can send our SMS reply!

Send an SMS to your caller from your Twilio number

Now we can create a new method in our application that sends our caller a message with Twilio's SMS API. To do that, we'll first need to include our Twilio authentication information.

Manage your secret keys with environment variables

To send an SMS via the API, you’ll need to include the Account SID and authentication token for your Twilio account.

However, we don’t want to expose this information to anyone, so we’ll store that information in a .env file instead of hard-coding it in a php file.

Create a file named .env at the root of your project. It should contain two lines:

export ACCOUNT_SID=ACXXXXXXXXXXXXXXXXXX
export AUTH_TOKEN=your_auth_token

You can find your unique Account SID and Auth Token in your Twilio console. Replace the placeholder values with your SID and token and save the file.

To enable this variables inside the app, remember to load the file into your environment before running the app:

source .env

Now your application can safely access your Twilio authentication information.

Programmatically send an SMS from the call

Now it’s time to send a message to our caller!

Update your index.php file to include a sendSms method that sends the caller a clue:

<?php
namespace TwilioDevEd;

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Factory\AppFactory;
use Twilio\TwiML\VoiceResponse;
use Twilio\Rest\Client;
use Twilio\Exceptions\RestException;

class App
{
  /**
   * Stores an instance of the Slim application.
   *
   * @var \Slim\App
   */
  private $app;

  public function __construct() {
    $app = AppFactory::create();
    $app->post('/answer', function (Request $request, Response $response, array $args) {
      $parsedBody = $request->getParsedBody();
      $caller = $parsedBody['From'];
      $twilioNumber = $parsedBody['To'];
      $this->sendSms($caller, $twilioNumber);

      $twilioResponse = new VoiceResponse();
      $twilioResponse->say('Thanks for calling! We just sent you a text with a clue.', ['voice' => 'alice']);

      $response->getBody()->write(strval($twilioResponse));
      return $response;
    });

    $app->get('/', function (Request $request, Response $response, array $args) {
      $response->getBody()->write("Please configure your Twilio number to use the /answer endpoint");
      return $response;
    });


    $this->app = $app;
  }

  public function sendSms(string $toNumber, string $fromNumber, Client $client = null): bool {
      $accountSid = getenv('ACCOUNT_SID');
      $authToken = getenv('AUTH_TOKEN');
      $client = $client ?? new Client($accountSid, $authToken);

      try {
          $client->messages->create(
              $toNumber,
              [
                  'from' => $fromNumber,
                  'body' => "There's always money in the banana stand.",
              ]
          );
      } catch (RestException $e) {
          if ($e->getStatusCode() == 21614) {
              echo "Uh oh, looks like this caller can't receive SMS messages.";
              return false;
          }
      }
      return true;
  }


  /**
   * Get an instance of the application.
   *
   * @return \Slim\App
   */
  public function get()
  {
      return $this->app;
  }
}

Now, update the /answer route to execute this new sendSms function. We’ll pass along the From and To values found in the initial request.

<?php
namespace TwilioDevEd;

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Factory\AppFactory;
use Twilio\TwiML\VoiceResponse;
use Twilio\Rest\Client;
use Twilio\Exceptions\RestException;

class App
{
  /**
   * Stores an instance of the Slim application.
   *
   * @var \Slim\App
   */
  private $app;

  public function __construct() {
    $app = AppFactory::create();
    $app->post('/answer', function (Request $request, Response $response, array $args) {
      $parsedBody = $request->getParsedBody();
      $caller = $parsedBody['From'];
      $twilioNumber = $parsedBody['To'];
      $this->sendSms($caller, $twilioNumber);

      $twilioResponse = new VoiceResponse();
      $twilioResponse->say('Thanks for calling! We just sent you a text with a clue.', ['voice' => 'alice']);

      $response->getBody()->write(strval($twilioResponse));
      return $response;
    });

    $app->get('/', function (Request $request, Response $response, array $args) {
      $response->getBody()->write("Please configure your Twilio number to use the /answer endpoint");
      return $response;
    });


    $this->app = $app;
  }

  public function sendSms(string $toNumber, string $fromNumber, Client $client = null): bool {
      $accountSid = getenv('ACCOUNT_SID');
      $authToken = getenv('AUTH_TOKEN');
      $client = $client ?? new Client($accountSid, $authToken);

      try {
          $client->messages->create(
              $toNumber,
              [
                  'from' => $fromNumber,
                  'body' => "There's always money in the banana stand.",
              ]
          );
      } catch (RestException $e) {
          if ($e->getStatusCode() == 21614) {
              echo "Uh oh, looks like this caller can't receive SMS messages.";
              return false;
          }
      }
      return true;
  }


  /**
   * Get an instance of the application.
   *
   * @return \Slim\App
   */
  public function get()
  {
      return $this->app;
  }
}

Save your file and double check that ngrok is still running. If you restart ngrok, you’ll need to reset your webhook for your Twilio phone number via the console.

Try calling your Twilio phone number again. Now you should hear your updated message and get an SMS with a secret!

If you send messages while in trial mode, you must first verify your 'To' phone number so Twilio knows you own it. If you attempt to send an SMS from your trial account to an unverified number, the API will return Error 21219.

You can verify your phone number by adding it to your Verified Caller IDs in the console.

What about landlines?

This app doesn’t know if our caller is dialing from a phone that accepts SMS or not. What happens if someone calls from a landline and can’t receive an SMS?

Twilio handles an attempt to send an SMS to a landline differently depending on the location of the SMS recipient.

If you try to send an SMS to a landline in the US, Canada, or the UK, Twilio will not check if the number is a landline first. Instead, it will attempt to send the message to the carrier for delivery. Some carriers (especially those in the UK) will convert the SMS to a text-to-speech message via a voice call.

To learn how to see if your SMS was delivered to your recipient, see our tutorial on tracking the delivery status of your message.

If your recipient is located elsewhere, the Twilio REST API will throw Error 21614, and the message will not appear in your logs.

To better handle exceptions on a call from someone who used a landline to dial our Twilio number, we’ll build some error handling right into our app:

<?php
namespace TwilioDevEd;

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Factory\AppFactory;
use Twilio\TwiML\VoiceResponse;
use Twilio\Rest\Client;
use Twilio\Exceptions\RestException;

class App
{
  /**
   * Stores an instance of the Slim application.
   *
   * @var \Slim\App
   */
  private $app;

  public function __construct() {
    $app = AppFactory::create();
    $app->post('/answer', function (Request $request, Response $response, array $args) {
      $parsedBody = $request->getParsedBody();
      $caller = $parsedBody['From'];
      $twilioNumber = $parsedBody['To'];
      $this->sendSms($caller, $twilioNumber);

      $twilioResponse = new VoiceResponse();
      $twilioResponse->say('Thanks for calling! We just sent you a text with a clue.', ['voice' => 'alice']);

      $response->getBody()->write(strval($twilioResponse));
      return $response;
    });

    $app->get('/', function (Request $request, Response $response, array $args) {
      $response->getBody()->write("Please configure your Twilio number to use the /answer endpoint");
      return $response;
    });


    $this->app = $app;
  }

  public function sendSms(string $toNumber, string $fromNumber, Client $client = null): bool {
      $accountSid = getenv('ACCOUNT_SID');
      $authToken = getenv('AUTH_TOKEN');
      $client = $client ?? new Client($accountSid, $authToken);

      try {
          $client->messages->create(
              $toNumber,
              [
                  'from' => $fromNumber,
                  'body' => "There's always money in the banana stand.",
              ]
          );
      } catch (RestException $e) {
          if ($e->getStatusCode() == 21614) {
              echo "Uh oh, looks like this caller can't receive SMS messages.";
              return false;
          }
      }
      return true;
  }


  /**
   * Get an instance of the application.
   *
   * @return \Slim\App
   */
  public function get()
  {
      return $this->app;
  }
}

With this exception handling, our caller will still hear our voice response but won’t receive the text.

In our sample app, we’re printing the error message and gracefully exiting. If you were running this app in production, you could handle this exception any way you like: you might track these occurrences in your application logs or spin up a response voice call to your caller.

What's next?

Now that you know how to leverage both Twilio’s Voice and SMS APIs to send a text message when someone calls your Twilio number, you may want to go deeper:

We can’t wait to see what you build!