# Workflow Automation with PHP and Laravel

One of the more abstract concepts you'll handle when building your business is what the *workflow* will look like.

At its core, setting up a standardized workflow is about enabling your service providers (agents, hosts, customer service reps, administrators, and the rest of the gang) to better serve your customers.

To illustrate a very real-world example, today we'll build a PHP and Laravel webapp for finding and booking vacation properties — tentatively called ***Airtng***.

Here's how it'll work:

1. A *host* creates a vacation property listing
2. A *guest* requests a reservation for a property
3. The *host* receives an SMS notifying them of the reservation request. The host can either **Accept** or **Reject** the reservation
4. The *guest* is notified whether a request was rejected or accepted

*[Learn how Airbnb used Twilio SMS to streamline the rental experience for 60M+ travelers around the world.](https://customers.twilio.com/en-us/airbnb)*

## Workflow Building Blocks

We'll be using the Twilio REST API to send our users messages at important junctures. Here's a bit more on our API:

* Sending Messages with Twilio API

```php title="Routes for handling reservations or Twilio webhooks"
<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get(
    '/', ['as' => 'home', function () {
        return response()->view('home');
    }]
);

// Session related routes
Route::get(
    '/auth/login',
    ['as' => 'login-index', function () {
        return response()->view('login');
    }]
);

Route::get(
    '/login',
    ['as' => 'login-index', function () {
        return response()->view('login');
    }]
);

Route::post(
    '/login',
    ['uses' => 'SessionController@login', 'as' => 'login-action']
);

Route::get(
    '/logout',
    ['as' => 'logout', function () {
        Auth::logout();
        return redirect()->route('home');
    }]
);

// User related routes
Route::get(
    '/user/new',
    ['as' => 'user-new', function () {
        return response()->view('newUser');
    }]
);

Route::post(
    '/user/create',
    ['uses' => 'UserController@createNewUser', 'as' => 'user-create', ]
);

// Vacation Property related routes
Route::get(
    '/property/new',
    ['as' => 'property-new',
     'middleware' => 'auth',
     function () {
         return response()->view('property.newProperty');
     }]
);

Route::get(
    '/properties',
    ['as' => 'property-index',
     'middleware' => 'auth',
     'uses' => 'VacationPropertyController@index']
);

Route::get(
    '/property/{id}',
    ['as' => 'property-show',
     'middleware' => 'auth',
     'uses' => 'VacationPropertyController@show']
);

Route::get(
    '/property/{id}/edit',
    ['as' => 'property-edit',
     'middleware' => 'auth',
     'uses' => 'VacationPropertyController@editForm']
);

Route::post(
    '/property/edit/{id}',
    ['uses' => 'VacationPropertyController@editProperty',
     'middleware' => 'auth',
     'as' => 'property-edit-action']
);

Route::post(
    '/property/create',
    ['uses' => 'VacationPropertyController@createNewProperty',
     'middleware' => 'auth',
     'as' => 'property-create']
);

// Reservation related routes
Route::post(
    '/property/{id}/reservation/create',
    ['uses' => 'ReservationController@create',
     'as' => 'reservation-create',
     'middleware' => 'auth']
);

Route::post(
    '/reservation/incoming',
    ['uses' => 'ReservationController@acceptReject',
     'as' => 'reservation-incoming']
);
```

Let's boldly go to the next step! Hit the button below to begin.

## User and Session Management

Our workflow will require allowing users to create accounts and log-in in order to attempt to reserve properties.

Each `User` will need to have a `phone_number` which will be required to send SMS notifications later.

```php title="User table migration" description="database/migrations/2014_10_12_000000_create_users_table.php"
// !mark(15:24)
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->string('password', 60);
            $table->string('phone_number');
            $table->string('country_code');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('users');
    }
}
```

Next up, we will create a table that represents a Vacation Rental property.

## Vacation Property

We're going to need a way to create the property listings for ***Airtng*** to be a success.

The `VacationProperty` model belongs to a `User` who created it (we'll call this user the *host* moving forward) and contains only two properties: a `description` and an `image_url`.

It has two associations: it has *many* reservations and *many* users through those reservations.

```php title="VacationProperty table migration" description="database/migrations/2015_10_23_193814_create_vacation_properties_table.php"
// !mark(15:25)
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateVacationPropertiesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('vacation_properties', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id')->unsigned();
            $table->foreign('user_id')
                  ->references('id')->on('users')
                  ->onDelete('cascade');
            $table->string('description');
            $table->string('image_url');

            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('vacation_properties');
    }
}
```

Next at the plate: how we will model a reservation.

## Our Reservation Model

The `Reservation` model is at the center of the workflow for this application. It is responsible for keeping track of:

* The `guest` who performed the reservation
* The `vacation_property` the guest is requesting (and associated *host*)
* The `status` of the reservation: `pending`, `confirmed`, or `rejected`

Since the reservation can only have one guest in this example, we simplified the model by assigning `phone_number` directly to the model (but you'll want to move it out).

```php title="Reservations table migration" description="database/migrations/2015_10_23_194614_create_reservations_table.php"
// !mark(15:31)
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateReservationsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('reservations', function (Blueprint $table) {
            $table->increments('id');
            $table->string('status')
                  ->default('pending');
            $table->string('respond_phone_number');
            $table->text('message');
            $table->integer('vacation_property_id')->unsigned();
            $table->foreign('vacation_property_id')
                  ->references('id')->on('vacation_properties')
                  ->onDelete('cascade');
            $table->integer('user_id')->unsigned();
            $table->foreign('user_id')
                  ->references('id')->on('users')
                  ->onDelete('cascade');

            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('reservations');
    }
}
```

Our tables are ready, now let's see how we would create a reservation.

## Reservation Creation

The reservation creation form holds only a single field: the message that will be sent to the *host* user when reserving one of her properties.

The rest of the information necessary to create a reservation is taken from the user that is logged into the system and the relationship between a property and its owner.

A reservation is created with a default status `pending`, so when the *host* replies with a `confirm` or `reject` response the system knows which reservation to update.

```php title="Create a new reservation" description="app/Http/Controllers/ReservationController.php"
// !mark(21:42)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\Authenticatable;
use App\Reservation;
use App\User;
use App\VacationProperty;
use Twilio\Rest\Client;
use Twilio\TwiML\MessagingResponse;

class ReservationController extends Controller
{
    /**
     * Store a new reservation
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function create(Client $client, Request $request, Authenticatable $user, $id)
    {
        $this->validate(
            $request, [
                'message' => 'required|string'
            ]
        );
        $property = VacationProperty::find($id);
        $reservation = new Reservation($request->all());
        $reservation->respond_phone_number = $user->fullNumber();
        $reservation->user()->associate($property->user);

        $property->reservations()->save($reservation);

        $this->notifyHost($client, $reservation);

        $request->session()->flash(
            'status',
            "Sending your reservation request now."
        );
        return redirect()->route('property-show', ['id' => $property->id]);
    }

    public function acceptReject(Request $request)
    {
        $hostNumber = $request->input('From');
        $smsInput = strtolower($request->input('Body'));

        $host = User::getUsersByFullNumber($hostNumber)->first();
        $reservation = $host->pendingReservations()->first();

        $smsResponse = null;

        if (!is_null($reservation))
        {
            if (strpos($smsInput, 'yes') !== false || strpos($smsInput, 'accept') !== false)
            {
                $reservation->confirm();
            }
            else
            {
                $reservation->reject();
            }

            $smsResponse = 'You have successfully ' . $reservation->status . ' the reservation.';
        }
        else
        {
            $smsResponse = 'Sorry, it looks like you don\'t have any reservations to respond to.';
        }

        return response($this->respond($smsResponse, $reservation))->header('Content-Type', 'application/xml');
    }

    private function respond($smsResponse, $reservation)
    {
        $response = new MessagingResponse();
        $response->message($smsResponse);

        if (!is_null($reservation))
        {
            $response->message(
                'Your reservation has been ' . $reservation->status . '.',
                ['to' => $reservation->respond_phone_number]
            );
        }
        return $response;
    }

    private function notifyHost($client, $reservation)
    {
        $host = $reservation->property->user;

        $twilioNumber = config('services.twilio')['number'];
        $messageBody = $reservation->message . ' - Reply \'yes\' or \'accept\' to confirm the reservation, or anything else to reject it.';

        try {
            $client->messages->create(
                $host->fullNumber(), // Text any number
                [
                    'from' => $twilioNumber, // From a Twilio number in your account
                    'body' => $messageBody
                ]
            );
        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }
}
```

Let's take a look at how the SMS notification is sent to the host when the reservation is created.

## Notify the Host

When a reservation is created for a property, we want to notify the owner of that property that someone has requested a reservation.

This is where we use Twilio's Rest API to send an SMS message to the *host*, using our [Twilio phone number](https://twilio.com/console). Sending SMS messages using Twilio takes just a few lines of code.

Now we just have to wait for the host to send an SMS response to 'accept' or 'reject', notify the guest, and update the reservation.

```php title="Notify the host of a new reservation request" description="app/Http/Controllers/ReservationController.php"
// !mark(90:108)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\Authenticatable;
use App\Reservation;
use App\User;
use App\VacationProperty;
use Twilio\Rest\Client;
use Twilio\TwiML\MessagingResponse;

class ReservationController extends Controller
{
    /**
     * Store a new reservation
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function create(Client $client, Request $request, Authenticatable $user, $id)
    {
        $this->validate(
            $request, [
                'message' => 'required|string'
            ]
        );
        $property = VacationProperty::find($id);
        $reservation = new Reservation($request->all());
        $reservation->respond_phone_number = $user->fullNumber();
        $reservation->user()->associate($property->user);

        $property->reservations()->save($reservation);

        $this->notifyHost($client, $reservation);

        $request->session()->flash(
            'status',
            "Sending your reservation request now."
        );
        return redirect()->route('property-show', ['id' => $property->id]);
    }

    public function acceptReject(Request $request)
    {
        $hostNumber = $request->input('From');
        $smsInput = strtolower($request->input('Body'));

        $host = User::getUsersByFullNumber($hostNumber)->first();
        $reservation = $host->pendingReservations()->first();

        $smsResponse = null;

        if (!is_null($reservation))
        {
            if (strpos($smsInput, 'yes') !== false || strpos($smsInput, 'accept') !== false)
            {
                $reservation->confirm();
            }
            else
            {
                $reservation->reject();
            }

            $smsResponse = 'You have successfully ' . $reservation->status . ' the reservation.';
        }
        else
        {
            $smsResponse = 'Sorry, it looks like you don\'t have any reservations to respond to.';
        }

        return response($this->respond($smsResponse, $reservation))->header('Content-Type', 'application/xml');
    }

    private function respond($smsResponse, $reservation)
    {
        $response = new MessagingResponse();
        $response->message($smsResponse);

        if (!is_null($reservation))
        {
            $response->message(
                'Your reservation has been ' . $reservation->status . '.',
                ['to' => $reservation->respond_phone_number]
            );
        }
        return $response;
    }

    private function notifyHost($client, $reservation)
    {
        $host = $reservation->property->user;

        $twilioNumber = config('services.twilio')['number'];
        $messageBody = $reservation->message . ' - Reply \'yes\' or \'accept\' to confirm the reservation, or anything else to reject it.';

        try {
            $client->messages->create(
                $host->fullNumber(), // Text any number
                [
                    'from' => $twilioNumber, // From a Twilio number in your account
                    'body' => $messageBody
                ]
            );
        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }
}
```

Let's see how we would handle incoming messages from Twilio and accept or reject reservations.

## Handle Incoming Messages

We're zoomed in for a closer look at the `acceptReject` method. This method handles our incoming Twilio request and does three things:

1. Checks for a pending reservation from the incoming user
2. Updates the status of the reservation
3. Responds to the host and sends a notification to the guest

```php title="Accept or reject a reservation logic" description="app/Http/Controllers/ReservationController.php"
// !mark(43:88)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\Authenticatable;
use App\Reservation;
use App\User;
use App\VacationProperty;
use Twilio\Rest\Client;
use Twilio\TwiML\MessagingResponse;

class ReservationController extends Controller
{
    /**
     * Store a new reservation
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function create(Client $client, Request $request, Authenticatable $user, $id)
    {
        $this->validate(
            $request, [
                'message' => 'required|string'
            ]
        );
        $property = VacationProperty::find($id);
        $reservation = new Reservation($request->all());
        $reservation->respond_phone_number = $user->fullNumber();
        $reservation->user()->associate($property->user);

        $property->reservations()->save($reservation);

        $this->notifyHost($client, $reservation);

        $request->session()->flash(
            'status',
            "Sending your reservation request now."
        );
        return redirect()->route('property-show', ['id' => $property->id]);
    }

    public function acceptReject(Request $request)
    {
        $hostNumber = $request->input('From');
        $smsInput = strtolower($request->input('Body'));

        $host = User::getUsersByFullNumber($hostNumber)->first();
        $reservation = $host->pendingReservations()->first();

        $smsResponse = null;

        if (!is_null($reservation))
        {
            if (strpos($smsInput, 'yes') !== false || strpos($smsInput, 'accept') !== false)
            {
                $reservation->confirm();
            }
            else
            {
                $reservation->reject();
            }

            $smsResponse = 'You have successfully ' . $reservation->status . ' the reservation.';
        }
        else
        {
            $smsResponse = 'Sorry, it looks like you don\'t have any reservations to respond to.';
        }

        return response($this->respond($smsResponse, $reservation))->header('Content-Type', 'application/xml');
    }

    private function respond($smsResponse, $reservation)
    {
        $response = new MessagingResponse();
        $response->message($smsResponse);

        if (!is_null($reservation))
        {
            $response->message(
                'Your reservation has been ' . $reservation->status . '.',
                ['to' => $reservation->respond_phone_number]
            );
        }
        return $response;
    }

    private function notifyHost($client, $reservation)
    {
        $host = $reservation->property->user;

        $twilioNumber = config('services.twilio')['number'];
        $messageBody = $reservation->message . ' - Reply \'yes\' or \'accept\' to confirm the reservation, or anything else to reject it.';

        try {
            $client->messages->create(
                $host->fullNumber(), // Text any number
                [
                    'from' => $twilioNumber, // From a Twilio number in your account
                    'body' => $messageBody
                ]
            );
        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }
}
```

In order to route an SMS messages to and from the host, we need to setup Twilio webhooks. The next pane will show you the way.

## Handle Incoming Twilio Requests

This method handles the Twilio request triggered by the host's SMS and does three things:

1. Checks for a pending reservation from a user
2. Updates the status of the reservation
3. Responds to the host and sends a notification to the user

### Setting Up Incoming Twilio Requests

In the [Twilio console](https://twilio.com/console), you should change the 'A Message Comes In' webhook to call your application's endpoint in the route `/reservation/incoming:`

![Messaging configuration with webhook URL set to https://yourserver.com/sms.](https://docs-resources.prod.twilio.com/f25966b33e960a4214a186c1dbdd0d35aff94f3b3c704f7db85ba8a082ec20d2.png)

One way to expose your machine to the world during development is to use [ngrok](https://ngrok.com/). Your URL for the SMS web hook on your phone number should look something like this:

```bash
http://<subdomain>.ngrok.io/reservation/incoming

```

An incoming request from Twilio comes with some helpful [parameters](/docs/messaging/twiml). These include the `From` phone number and the message `Body`.

We'll use the `From` parameter to look up the host and check if they have any pending reservations. If they do, we'll use the message body to check for the message 'accepted' or 'rejected'. Finally, we update the reservation status and send an SMS to the *guest* telling them the host accepted or rejected their reservation request.

#### TwiML Response

In our response to Twilio, we'll use [Twilio's TwiML](/docs/glossary/what-is-twilio-markup-language-twiml) to command Twilio to send an SMS notification message to the *host.*

```php title="Finding a reservation from an incoming Twilio request" description="app/Http/Controllers/ReservationController.php"
// !mark(47:52)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\Authenticatable;
use App\Reservation;
use App\User;
use App\VacationProperty;
use Twilio\Rest\Client;
use Twilio\TwiML\MessagingResponse;

class ReservationController extends Controller
{
    /**
     * Store a new reservation
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function create(Client $client, Request $request, Authenticatable $user, $id)
    {
        $this->validate(
            $request, [
                'message' => 'required|string'
            ]
        );
        $property = VacationProperty::find($id);
        $reservation = new Reservation($request->all());
        $reservation->respond_phone_number = $user->fullNumber();
        $reservation->user()->associate($property->user);

        $property->reservations()->save($reservation);

        $this->notifyHost($client, $reservation);

        $request->session()->flash(
            'status',
            "Sending your reservation request now."
        );
        return redirect()->route('property-show', ['id' => $property->id]);
    }

    public function acceptReject(Request $request)
    {
        $hostNumber = $request->input('From');
        $smsInput = strtolower($request->input('Body'));

        $host = User::getUsersByFullNumber($hostNumber)->first();
        $reservation = $host->pendingReservations()->first();

        $smsResponse = null;

        if (!is_null($reservation))
        {
            if (strpos($smsInput, 'yes') !== false || strpos($smsInput, 'accept') !== false)
            {
                $reservation->confirm();
            }
            else
            {
                $reservation->reject();
            }

            $smsResponse = 'You have successfully ' . $reservation->status . ' the reservation.';
        }
        else
        {
            $smsResponse = 'Sorry, it looks like you don\'t have any reservations to respond to.';
        }

        return response($this->respond($smsResponse, $reservation))->header('Content-Type', 'application/xml');
    }

    private function respond($smsResponse, $reservation)
    {
        $response = new MessagingResponse();
        $response->message($smsResponse);

        if (!is_null($reservation))
        {
            $response->message(
                'Your reservation has been ' . $reservation->status . '.',
                ['to' => $reservation->respond_phone_number]
            );
        }
        return $response;
    }

    private function notifyHost($client, $reservation)
    {
        $host = $reservation->property->user;

        $twilioNumber = config('services.twilio')['number'];
        $messageBody = $reservation->message . ' - Reply \'yes\' or \'accept\' to confirm the reservation, or anything else to reject it.';

        try {
            $client->messages->create(
                $host->fullNumber(), // Text any number
                [
                    'from' => $twilioNumber, // From a Twilio number in your account
                    'body' => $messageBody
                ]
            );
        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }
}
```

In the last step, we'll respond to Twilio's request with some TwiML instructing it to send an SMS to both the *host* and *guest*.

## TwiML Response

After updating the reservation status, we must notify the *host* that they have successfully confirmed or rejected the reservation. If the host has no pending reservation, we'll instead return an error message.

If we're modifying a reservation, we'll also send a message to the user who requested the rental delivering the happy or sad news.

We use the [Message verb](/docs/messaging/twiml/message) from TwiML to instruct Twilio to send two SMS messages.

```php title="Respond to a user with reservation status" description="app/Http/Controllers/ReservationController.php"
// !mark(75:88)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\Authenticatable;
use App\Reservation;
use App\User;
use App\VacationProperty;
use Twilio\Rest\Client;
use Twilio\TwiML\MessagingResponse;

class ReservationController extends Controller
{
    /**
     * Store a new reservation
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function create(Client $client, Request $request, Authenticatable $user, $id)
    {
        $this->validate(
            $request, [
                'message' => 'required|string'
            ]
        );
        $property = VacationProperty::find($id);
        $reservation = new Reservation($request->all());
        $reservation->respond_phone_number = $user->fullNumber();
        $reservation->user()->associate($property->user);

        $property->reservations()->save($reservation);

        $this->notifyHost($client, $reservation);

        $request->session()->flash(
            'status',
            "Sending your reservation request now."
        );
        return redirect()->route('property-show', ['id' => $property->id]);
    }

    public function acceptReject(Request $request)
    {
        $hostNumber = $request->input('From');
        $smsInput = strtolower($request->input('Body'));

        $host = User::getUsersByFullNumber($hostNumber)->first();
        $reservation = $host->pendingReservations()->first();

        $smsResponse = null;

        if (!is_null($reservation))
        {
            if (strpos($smsInput, 'yes') !== false || strpos($smsInput, 'accept') !== false)
            {
                $reservation->confirm();
            }
            else
            {
                $reservation->reject();
            }

            $smsResponse = 'You have successfully ' . $reservation->status . ' the reservation.';
        }
        else
        {
            $smsResponse = 'Sorry, it looks like you don\'t have any reservations to respond to.';
        }

        return response($this->respond($smsResponse, $reservation))->header('Content-Type', 'application/xml');
    }

    private function respond($smsResponse, $reservation)
    {
        $response = new MessagingResponse();
        $response->message($smsResponse);

        if (!is_null($reservation))
        {
            $response->message(
                'Your reservation has been ' . $reservation->status . '.',
                ['to' => $reservation->respond_phone_number]
            );
        }
        return $response;
    }

    private function notifyHost($client, $reservation)
    {
        $host = $reservation->property->user;

        $twilioNumber = config('services.twilio')['number'];
        $messageBody = $reservation->message . ' - Reply \'yes\' or \'accept\' to confirm the reservation, or anything else to reject it.';

        try {
            $client->messages->create(
                $host->fullNumber(), // Text any number
                [
                    'from' => $twilioNumber, // From a Twilio number in your account
                    'body' => $messageBody
                ]
            );
        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }
}
```

Congratulations! We've just automated a rental workflow with Twilio's Programmable SMS, and now you're ready to add it to your own application.

Next, let's take a look at some other features you might like to add.

## What to Next?

PHP + Twilio? Excellent choice! Here are a couple other tutorials for you to try:

[**Click To Call**](/docs/voice/sdks/javascript/get-started)

Put a button on your web page that connects visitors to live support or salespeople via telephone.

[**Automated Survey**](/blog/automated-survey-php-laravel)

Instantly collect structured data from your users with a survey conducted over a voice call or SMS text messages.
