Answer Support Calls, Then Handle Them Using SMS in PHP

June 08, 2026
Written by
Reviewed by

Answer Support Calls, Then Handle Them Using SMS in PHP

It would be great if every organisation — regardless of its size — could have a 24/7 support center, where customers can talk with a customer representative any time, day or night. However, the reality is that that's not always feasible.

But, that doesn't mean that, for the hours where you can't provide active support, your customers are left with no support whatsoever. With Twilio and PHP you could build an automated service which answers your customers' most-commonly-asked questions and handles basic tasks.

For example, it could answer the following questions:

  • What are your company's opening hours?
  • What are your support, billing, and account email addresses?
  • What is your postal address?

And, some basic tasks might be booking an in-person appointment, or requesting a callback during office hours.

If that's something your business could benefit from, then in this tutorial, you're going to learn how to build such a system using TwiML (the Twilio Markup Language) and PHP.

Let's begin!

About the application

Here's how the application will work. It is a small API with three endpoints.

Endpoint

Path

Description

Default

/

Requests to this endpoint are made by Twilio when a phone call is received to your Twilio phone number. 

The endpoint returns TwiML instructing Twilio to tell the customer that they are being redirected to support, and that they will receive a follow-up SMS with the available self-help support options.

Following that, Twilio is redirected to the "/send-sms" endpoint for the instructions for sending the follow-up SMS to the caller.

Support

/support

Requests to this endpoint are made by Twilio when an SMS is received by your Twilio phone number. The endpoint returns TwiML instructing Twilio to:

  1. Send a reply SMS with the one of the following:

    • The organisation's opening hours

    • The general support email address

    • The account and billing support email address

    • The business' mailing address

  2. Request a callback

  3. Make an in-person appointment

Note: You won't implement the functionality for options 2 or 3.

Send Explanatory SMS

/send-sms

Requests to this endpoint are made by Twilio after the initial phone call is received. The endpoint sends an SMS to the caller with the self-service support options.

Prerequisites

You'll need the following to use the application:

  • A Twilio account (free or paid) with a phone number that supports both SMS and calls. Create a Twilio account if you don't already have one.
  • PHP 8.5 or above
  • Composer globally installed
  • Your preferred code editor or IDE, such as neovim or Visual Studio Code
  • Some terminal experience is helpful, though not required

Set up the base PHP project

With the prerequisites in place, the first thing to do is to bootstrap a base PHP project. To save time, you'll use the Twilio / Slim Base project. If you've not heard of it, it's a small PHP app powered by the Slim framework, designed to save you time getting started building Twilio projects in PHP.

Run the following command to generate a new application from it and change into the project's top-level directory:

composer create-project \
    settermjd/twilio-slim-base-project \
    inbound-call-with-text-response

cd inbound-call-with-text-response

Add the ability to handle incoming support calls

Begin by implementing the default endpoint ("/"). It will let the caller know that they have called out of business/support hours, and start the automated support process.

To do that, in src/Application.php, replace the handleDefaultRoute() function with the following:

public function handleIncomingCall(
    ServerRequestInterface $request,
    ResponseInterface $response,
): ResponseInterface {
    $twimlResponse = new VoiceResponse();
    $twimlResponse->say(self::SUPPORT_REDIRECT);
    $twimlResponse->redirect("./send-sms", ["method" => "POST"]);
    $twimlResponse->hangup();

    $response = $response->withHeader("content-type", "application/xml");
    $response->getBody()->write($twimlResponse->asXML());
    return $response;
}

The code generates a TwiML Voice response and writes it as the body of the response, along with the appropriate content-type header.

The generated TwiML looks like the following example (formatted for better readability):

<?xml version="1.0" encoding="UTF-8"?>
<Response>
    <Say>
We're not able to handle support calls directly at the moment.
However, we can provide limited support via SMS.
We'll send you an SMS in a moment with the available options.
    </Say>
    <Redirect method="POST">./send-sms</Redirect>
    <Hangup></Hangup>
</Response>

What it means is that when a call is made to your Twilio phone number (after it's configured) it will:

  1. Speak the text in the Say verb
  2. Redirect Twilio to the "/send-sms" endpoint
  3. End the call

With the function added, add the following use statements to the list at the top of the file.

use Twilio\TwiML\VoiceResponse;

use function is_string;

Then, add the following class constant used in the function.

public const string SUPPORT_REDIRECT = <<<EOF
We're not able to handle support calls directly at the moment.
However, we can provide limited support via SMS.
We'll send you an SMS in a moment with the available options.
EOF;
I've used HEREDOC comments heavily in the class, mainly with the intention of better readability.

The last change to make is in the setupRoutes() function. Change the default route's handler to 'handleIncomingCall', to match the new name of the default handler function.

public function setupRoutes(): void
{
    $this->app->post('/', [$this, 'handleIncomingCall']);
}

Add the ability to send a follow-up SMS

With the application able to handle incoming calls, next, add the ability to send the follow-up SMS after Twilio is redirected to the "/send-sms" endpoint.

To do that, add the following function after the handleIncomingCall() function, in src/Application.php.

public function handleSendInitialSupportSms(
    ServerRequestInterface $request,
    ResponseInterface $response,
): ResponseInterface {
    $requestData = (array) $request->getParsedBody();
    assert(is_string($requestData["From"]));
    $client = $this->app->getContainer()?->get(Client::class);
    if ($client instanceof Client) {
        $message = $client
            ->messages
            ->create(
                $requestData["From"],
                [
                    "body" => self::SUPPORT_OPTIONS,
                    "from" => $requestData["To"],
                ],
            );
    }

    $response = $response->withHeader("content-type", "application/xml");
    $response->getBody()->write(new VoiceResponse()->asXML());

    return $response;
}

The function is quite short. It also might be familiar to you if you've read some of my other Twilio tutorials, such as how to send an SMS with PHP in 30 seconds. It uses the Twilio Rest Client to send an SMS with the available, self-help options. Then, it sets an empty TwiML Voice response as the body of the response.

When requested, endpoints should always return at least a minimal TwiML response.

With the new function added, add the following use statement to the top of the class.

use Twilio\Rest\Client;

Then, update the setupRoutes() function as follows, to register the new "/support" route.

public function setupRoutes(): void
{
    $this->app->post('/', [$this, 'handleIncomingCall']);
    $this->app->post('/support', [$this, 'handleSupportRequestsBySms']);
}

Add the ability to handle support questions with SMS

Finally, add the ability to respond to self-help SMS. To do that, add the following function after handleSendInitialSupportSms() in src/Application.php.

public function handleSupportRequestsBySms(
    ServerRequestInterface $request,
    ResponseInterface $response,
): ResponseInterface {
    $requestData = (array) $request->getParsedBody();
    $twimlResponse = new MessagingResponse();
    if (! array_key_exists('Body', $requestData)) {
        $twimlResponse->message(self::SUPPORT_OPTIONS, [
            'to'   => $requestData['From'],
            'from' => $requestData['To'],
        ]);
    } else {
        $body = $requestData['Body'];
        assert(is_string($body));
        $message = match (true) {
            $this->isMatch($body, self::OPTIONS_OPENING_HOURS) => self::OPENING_HOURS,
            $this->isMatch($body, 'support email') => self::SUPPORT_EMAIL,
            $this->isMatch($body, self::OPTIONS_ACCOUNT_BILLING_SUPPORT_EMAIL) => self::ACCOUNT_BILLIING_EMAIL,
            $this->isMatch($body, 'postal address') => self::POSTAL_ADDRESS,
            $this->isMatch($body, 'callback') => self::CALLBACK,
            $this->isMatch($body, self::OPTIONS_APPOINTMENT) => sprintf(
                self::APPOINTMENT,
                new DateTime('next thursday')->format('l, F jS'),
            ),
            default => <<<EOF
        Please choose from:
        - Account support email
        - Billing support email
        - Book appointment
        - Callback 
        - Opening Hours
        - Postal address
        - Support Email
        EOF,
        };
        $twimlResponse->message($message);
    }

    $response = $response->withHeader('content-type', 'application/xml');
    $response->getBody()->write($twimlResponse->asXML());
    return $response;
}

private function isMatch(string $body, string|array $possibleValues): bool
{
    if (is_string($possibleValues)) {
        return strcasecmp($body, $possibleValues) === 0;
    }

    foreach ($possibleValues as $message) {
        if (strcasecmp($body, $message) === 0) {
            return true;
        }
    }

    return false;
}

The function's a little bit verbose, but uses a match expression to determine how to respond to incoming SMS, based on the SMS' body. In short, if the message is one of the available options, it will generate TwiML with the applicable response. However, if the SMS message is not one of the available options, the generated TwiML will be a list of the available options.

The code deliberately performs a quite limited check on an incoming SMS' body. Feel free to expand on it, such as by using a regular expression to match more human-readable questions.

In the table below, you can see the support options and their accompanying responses.

Support Option

Response

"hours" or "opening hours"

"Our opening hours are Mon to Fri from 8:30 am to 5:30 pm, and Sat from 9:30 am to 1:30 pm."

"support email"

"For general support, email support@example.org."

"account support email", "account", "billing support email", or "billing"

"For account support, email accounts@example.org. For billing support, email billing@example.org."

"postal address"

"Our mailing address is 1234 Queen Street, Brisbane, QLD, 4000, Australia."

"callback"

"Thank you for registering for a phone callback. We'll call you within the next 30 minutes."

"appointment" or "booking appointment"

"Thank you for seeking an appointment. The next available appointment is at 9:45 am on <the following Thursday>".

<the following Thursday> will be replaced by the date of the following Thursday when the SMS is sent.

Next, add the following constants to the top of the class. These define most of the support options and their responses.

public const string ACCOUNT_BILLIING_EMAIL = <<<EOF
For account support, email accounts@example.org. For billing support, email billing@example.org."
EOF;

public const string APPOINTMENT = <<<EOF
Thank you for seeking an appointment.
The next available appointment is at 9:45 am on %s
EOF;

public const string POSTAL_ADDRESS = 'Our mailing address is 1234 Queen Street, Brisbane, QLD, 4000, Australia.';

public const string CALLBACK = <<<EOF
Thank you for registering for a phone callback.
We'll call you within the next 30 minutes.
EOF;

public const string SUPPORT_OPTIONS = <<<EOF
Please choose from the following support options:
- For our opening hours, reply with: "opening hours".
- For our postal address, reply with: "postal address".
- For our support email, reply with: "support email".
- For the account support email, reply with: "account support email".
- For the billing support email, reply with: "billing support email".
- To book an appointment, reply with: "book appointment".
- To request a callback, reply with: "callback".
EOF;

public const string OPENING_HOURS = <<<EOF
Our opening hours are Mon to Fri from 8:30 am to 5:30 pm, and Sat from 9:30 am to 1:30 pm."
EOF;

public const string SUPPORT_EMAIL = "For general support, email support@example.org.";
public const array OPTIONS_OPENING_HOURS = [
    'hours',
    'opening hours',
];

public const array OPTIONS_ACCOUNT_BILLING_SUPPORT_EMAIL = [
    'account support email',
    'account',
    'billing support email',
    'billing',
];

public const array OPTIONS_APPOINTMENT = [
    'appointment',
    'book appointment',
];

Then, add the following use statements to the existing list at the top of the class.

use DateTime;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App as SlimApp;
use Slim\Interfaces\RouteInterface;
use Slim\Middleware\ContentLengthMiddleware;
use Twilio\TwiML\MessagingResponse;
use Twilio\TwiML\VoiceResponse;

use function array_key_exists;
use function assert;
use function is_string;
use function sprintf;
use function strcasecmp;

Finally, update setupRoutes() as follows, to define the "/support" route.

public function setupRoutes(): void
{
    $this->app->post('/', [$this, 'handleIncomingCall']);
    $this->app->post('/support', [$this, 'handleSupportRequestsBySms']);
    $this->app->post('/send-sms', [$this, 'handleSendInitialSupportSms']);
}

Start the application

With the application ready to go, let's test that it works and see it in action. First, start the application with the pre-defined Composer "serve" script, by running the following command:

composer serve

You should see output in the terminal similar to the following, indicating that the application is listening on port 8080:

[Mon Jun  1 15:40:36 2026] PHP 8.5.6 Development Server (http://0.0.0.0:8080) started

Next, make the application publicly available using ngrok, by running the following command:

ngrok http 8080

You should see terminal output similar to the following:

A terminal window showing ngrok output.
A terminal window showing ngrok output.

Copy the Forwarding URL, which ends with "ngrok-free.app", and keep it handy as you'll use it in the next step.

Configure Twilio to handle incoming calls and SMS

Log in to the Twilio Console in your browser of choice. Under Products & Services > Voice > Overview, click Set up Voice. Then, in TwiML Apps, click Create TwiML App.

The Create new TwiML App dialog in the Twilio Console
The Create new TwiML App dialog in the Twilio Console

Next, in the Create new TwiML App dialog that appears, add a Friendly name for the app, such as "Answer a Call", and click Create. Now, in the Primary call control configuration section of the Voice tab, paste your ngrok Forwarding URL (which you copied earlier) as the value of the Request URL field, and leave the accompanying HTTP method field set to "HTTP POST".

Shows the configuration details of a TwiML app in the Twilio Console
Shows the configuration details of a TwiML app in the Twilio Console

Then, click the MESSAGING tab. There, set your ngrok Forwarding URL plus "/support" as the value of the Request URL field, and leave the accompanying HTTP method field set to "HTTP POST".

Twilio portal showing messaging settings for a project with fields for request URL and HTTP method.

Then, scroll to the bottom of the page and click Save.

Set the required environment variables

Then, open the Twilio Console in your browser of choice, open the Workbench by clicking the black and white arrow in the middle of the bottom of the page, and copy the Account SID and Auth Token as you can see in the screenshot below.

The Twilio Console with the Workbench visible, showing the redacted Account SID and Auth Token.
The Twilio Console with the Workbench visible, showing the redacted Account SID and Auth Token.

Then, set those values as the values of TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, respectively, in .env.

After that, in the left-hand side navigation menu, navigate through Products & Services > Numbers & senders > Overview.

There, from the Phone Numbers tab, copy a phone number that supports both SMS and Voice, and paste it into .env as the value of TWILIO_PHONE_NUMBER.

Make sure you copy the E.164 formatted version, not the local version, e.g., +12345678912.

Test that the application works as expected

Finally, it's time to use the application. Start by making a phone call to the Twilio phone number that you configured in One Console. You should hear the default message spoken to you, before the call ends. Shortly after that, you'll receive an SMS with the available support options.

After receiving it, start sending support SMS requests to your configured Twilio phone number, and seeing that the expected responses are sent back to you.

That's how to answer support calls, with a self-help SMS system in PHP

While some of the functionality was, effectively, stubbed, I hope you see just how simplistic a self-help SMS response service could be when using TwiML and PHP. If you'd like to know more, check out the links below. Otherwise, you can find the repository behind this tutorial on GitHub. Feel free to clone and play with the code, or submit issues and PRs if you'd like to improve it.

Matthew Setter is a PHP and Go Editor in the Twilio Voices team. He’s also the author of Mezzio Essentials and Deploy with Docker Compose . You can find him at msetter@twilio.com . He's also on LinkedIn and GitHub .

In the post's header image the SMS icon and the phone icon were created by Freepik on Flaticon</a>, and ElePHPant was source from: http://php.net/elephpant.php.