ETA Notifications with PHP and Laravel

Download the Code

Laundry On-Demand

ETA Notifications

Companies like Uber, TaskRabbit, and Instacart have built an entire industry around the fact that we, the customers, like to order things instantly, wherever we are.

The key to these services working is notifying customers instantly when things change.  Customers appreciate being in the loop on order status, and (of course) like to know when something changes.

Uber relies on Twilio SMS to keep customers up to date on their ridesharing requests. Learn more here.

In this tutorial, we'll build a notification system for a fake on-demand laundy service Laundr.io with PHP and Laravel.

Let's get started!  Click the below button to begin.

Trigger the Notifications

The delivery person's screen will have two buttons to update orders which are wired to the appropriate routes:

  1. Delivery person picks up laundry to be delivered ( /pickup )
  2. Delivery person is arriving at the customer's house (/deliver )

The buttons will suffice for our demo app.  In a production app you would likely use GPS to trigger deliver, but the concept is the same.

Loading Code Samples...
Language
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Order;
use Twilio\Rest\Client;
use Log;

class OrderController extends Controller
{
    public function index()
    {
        return view('index', ['orders' => Order::all()]);
    }

    public function show($id)
    {
        return view('show', ['order' => Order::find($id)]);
    }

    public function pickup(Client $client, Request $request, $id)
    {
        $order = Order::find($id);
        $order->status = 'Shipped';
        $order->notification_status = 'queued';
        $order->save();

        $callbackUrl = str_replace('/pickup', '', $request->url()) . '/notification/status/update';
        $this->sendMessage(
            $client,
            $order->phone_number,
            'Your laundry is done and on its way to you!',
            $callbackUrl
        );

        return redirect()->route('order.show', ['id' => $order->id]);
    }

    public function deliver(Client $client, Request $request, $id)
    {
        $order = Order::find($id);
        $order->status = 'Delivered';
        $order->notification_status = 'queued';
        $order->save();

        $callbackUrl = str_replace('/deliver', '', $request->url()) . '/notification/status/update';
        $this->sendMessage(
            $client,
            $order->phone_number,
            'Your laundry is arriving now.',
            $callbackUrl
        );

        return redirect()->route('order.index');
    }

    public function notificationStatus(Request $request, $id)
    {
        $order = Order::find($id);
        $order->notification_status = $request->input('MessageStatus');
        $order->save();
    }

    private function sendMessage($client, $to, $messageBody, $callbackUrl)
    {
        $twilioNumber = config('services.twilio')['number'];
        try {
            $client->messages->create(
                $to, // Text any number
                [
                    'from' => $twilioNumber, // From a Twilio number in your account
                    'body' => $messageBody,
                    'statusCallback' => $callbackUrl
                ]
            );
        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }
}
app/Http/Controllers/OrderController.php
Send SMS Notification for delivery status

app/Http/Controllers/OrderController.php

Now let's look at how we set up the PHP Twilio REST Client.

Setting Up the Twilio REST Client

Here we create an authenticated Twilio REST API client that we can use anytime we need to send a text message.

We initialize the client with our Twilio account credentials stored as environment variables.  You can find the Auth Token and Account SID in the console:

console credentials

Loading Code Samples...
Language
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Twilio\Rest\Client;

class TwilioRestClientProvider extends ServiceProvider
{
    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(
            Client::class, function ($app) {
                $accountSid = config('services.twilio')['accountSid'];
                $authToken = config('services.twilio')['authToken'];
                return new Client($accountSid, $authToken);
            }
        );
    }
}
app/Providers/TwilioRestClientProvider.php
Setting Up the Twilio REST Client

app/Providers/TwilioRestClientProvider.php

Next up, let's look at what we do with incoming notification triggers.

Handle Notification Triggers

In OrderController we extract the phone number stored in each order, and then simply send an SMS message with an appropriate message body.  Easy!

Loading Code Samples...
Language
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Order;
use Twilio\Rest\Client;
use Log;

class OrderController extends Controller
{
    public function index()
    {
        return view('index', ['orders' => Order::all()]);
    }

    public function show($id)
    {
        return view('show', ['order' => Order::find($id)]);
    }

    public function pickup(Client $client, Request $request, $id)
    {
        $order = Order::find($id);
        $order->status = 'Shipped';
        $order->notification_status = 'queued';
        $order->save();

        $callbackUrl = str_replace('/pickup', '', $request->url()) . '/notification/status/update';
        $this->sendMessage(
            $client,
            $order->phone_number,
            'Your laundry is done and on its way to you!',
            $callbackUrl
        );

        return redirect()->route('order.show', ['id' => $order->id]);
    }

    public function deliver(Client $client, Request $request, $id)
    {
        $order = Order::find($id);
        $order->status = 'Delivered';
        $order->notification_status = 'queued';
        $order->save();

        $callbackUrl = str_replace('/deliver', '', $request->url()) . '/notification/status/update';
        $this->sendMessage(
            $client,
            $order->phone_number,
            'Your laundry is arriving now.',
            $callbackUrl
        );

        return redirect()->route('order.index');
    }

    public function notificationStatus(Request $request, $id)
    {
        $order = Order::find($id);
        $order->notification_status = $request->input('MessageStatus');
        $order->save();
    }

    private function sendMessage($client, $to, $messageBody, $callbackUrl)
    {
        $twilioNumber = config('services.twilio')['number'];
        try {
            $client->messages->create(
                $to, // Text any number
                [
                    'from' => $twilioNumber, // From a Twilio number in your account
                    'body' => $messageBody,
                    'statusCallback' => $callbackUrl
                ]
            );
        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }
}
app/Http/Controllers/OrderController.php
Send SMS Notification for delivery status

app/Http/Controllers/OrderController.php

Next, let's look closer at how we send the SMS itself.

Send a SMS (or MMS) To the Customer

This code shows how we send the SMS with the Twilio PHP Client.

Better shown than said?  We agree - you can add an image of the order with the optional mediaUrl:

'mediaUrl' => 'http://lorempixel.com/image_output/fashion-q-c-640-480-1.jpg'

In addition to the required parameters (and optional mediaUrl), we can pass a statusCallback url to let us know if the message was delivered.

Loading Code Samples...
Language
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Order;
use Twilio\Rest\Client;
use Log;

class OrderController extends Controller
{
    public function index()
    {
        return view('index', ['orders' => Order::all()]);
    }

    public function show($id)
    {
        return view('show', ['order' => Order::find($id)]);
    }

    public function pickup(Client $client, Request $request, $id)
    {
        $order = Order::find($id);
        $order->status = 'Shipped';
        $order->notification_status = 'queued';
        $order->save();

        $callbackUrl = str_replace('/pickup', '', $request->url()) . '/notification/status/update';
        $this->sendMessage(
            $client,
            $order->phone_number,
            'Your laundry is done and on its way to you!',
            $callbackUrl
        );

        return redirect()->route('order.show', ['id' => $order->id]);
    }

    public function deliver(Client $client, Request $request, $id)
    {
        $order = Order::find($id);
        $order->status = 'Delivered';
        $order->notification_status = 'queued';
        $order->save();

        $callbackUrl = str_replace('/deliver', '', $request->url()) . '/notification/status/update';
        $this->sendMessage(
            $client,
            $order->phone_number,
            'Your laundry is arriving now.',
            $callbackUrl
        );

        return redirect()->route('order.index');
    }

    public function notificationStatus(Request $request, $id)
    {
        $order = Order::find($id);
        $order->notification_status = $request->input('MessageStatus');
        $order->save();
    }

    private function sendMessage($client, $to, $messageBody, $callbackUrl)
    {
        $twilioNumber = config('services.twilio')['number'];
        try {
            $client->messages->create(
                $to, // Text any number
                [
                    'from' => $twilioNumber, // From a Twilio number in your account
                    'body' => $messageBody,
                    'statusCallback' => $callbackUrl
                ]
            );
        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }
}
app/Http/Controllers/OrderController.php
Using Twilio PHP client to send an SMS

app/Http/Controllers/OrderController.php

The message delivery status updates are interesting.  Let's zoom in on those next.

Handling a Twilio Status Callback

Twilio will make a post request to this controller each time our message status changes to one of the following: queued, failed, sent, delivered, or undelivered.

We then update this notification_status on the Order and your own business logic would take it from there. This is an excellent place to add logic that would resend the message in the case of a message failure, or send out an automated survey when there is a successful delivery to the customer.

Loading Code Samples...
Language
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Order;
use Twilio\Rest\Client;
use Log;

class OrderController extends Controller
{
    public function index()
    {
        return view('index', ['orders' => Order::all()]);
    }

    public function show($id)
    {
        return view('show', ['order' => Order::find($id)]);
    }

    public function pickup(Client $client, Request $request, $id)
    {
        $order = Order::find($id);
        $order->status = 'Shipped';
        $order->notification_status = 'queued';
        $order->save();

        $callbackUrl = str_replace('/pickup', '', $request->url()) . '/notification/status/update';
        $this->sendMessage(
            $client,
            $order->phone_number,
            'Your laundry is done and on its way to you!',
            $callbackUrl
        );

        return redirect()->route('order.show', ['id' => $order->id]);
    }

    public function deliver(Client $client, Request $request, $id)
    {
        $order = Order::find($id);
        $order->status = 'Delivered';
        $order->notification_status = 'queued';
        $order->save();

        $callbackUrl = str_replace('/deliver', '', $request->url()) . '/notification/status/update';
        $this->sendMessage(
            $client,
            $order->phone_number,
            'Your laundry is arriving now.',
            $callbackUrl
        );

        return redirect()->route('order.index');
    }

    public function notificationStatus(Request $request, $id)
    {
        $order = Order::find($id);
        $order->notification_status = $request->input('MessageStatus');
        $order->save();
    }

    private function sendMessage($client, $to, $messageBody, $callbackUrl)
    {
        $twilioNumber = config('services.twilio')['number'];
        try {
            $client->messages->create(
                $to, // Text any number
                [
                    'from' => $twilioNumber, // From a Twilio number in your account
                    'body' => $messageBody,
                    'statusCallback' => $callbackUrl
                ]
            );
        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }
}
app/Http/Controllers/OrderController.php
Update Order notification_status after a Twilio Callback

app/Http/Controllers/OrderController.php

That's a wrap (a fold?)!

We've just implemented an on-demand notification service that alerts our customers when their laundry order is arriving.  Let's take a look at other features that are easy to add with Twilio.

Where to next?

We've got a lot of great PHP content here on the Docs site but we've selected just a couple you might like to visit next:

Workflow Automation

Increase your rate of response by automating the workflows that are key to your business. In this tutorial, learn how to build a ready-for-scale automated SMS workflow for a vacation rental company.

Masked Numbers

Protect your users' privacy by anonymously connecting them with Twilio Voice and SMS. Learn how to create disposable phone numbers on-demand so two users can communicate without exchanging personal information.

Did this help?

Thanks for checking this tutorial out! Let us know what you've built - or what you're building - on Twitter.

Mario Celi
Paul Kamp
Andrew Baker
Agustin Camino

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Order;
use Twilio\Rest\Client;
use Log;

class OrderController extends Controller
{
    public function index()
    {
        return view('index', ['orders' => Order::all()]);
    }

    public function show($id)
    {
        return view('show', ['order' => Order::find($id)]);
    }

    public function pickup(Client $client, Request $request, $id)
    {
        $order = Order::find($id);
        $order->status = 'Shipped';
        $order->notification_status = 'queued';
        $order->save();

        $callbackUrl = str_replace('/pickup', '', $request->url()) . '/notification/status/update';
        $this->sendMessage(
            $client,
            $order->phone_number,
            'Your laundry is done and on its way to you!',
            $callbackUrl
        );

        return redirect()->route('order.show', ['id' => $order->id]);
    }

    public function deliver(Client $client, Request $request, $id)
    {
        $order = Order::find($id);
        $order->status = 'Delivered';
        $order->notification_status = 'queued';
        $order->save();

        $callbackUrl = str_replace('/deliver', '', $request->url()) . '/notification/status/update';
        $this->sendMessage(
            $client,
            $order->phone_number,
            'Your laundry is arriving now.',
            $callbackUrl
        );

        return redirect()->route('order.index');
    }

    public function notificationStatus(Request $request, $id)
    {
        $order = Order::find($id);
        $order->notification_status = $request->input('MessageStatus');
        $order->save();
    }

    private function sendMessage($client, $to, $messageBody, $callbackUrl)
    {
        $twilioNumber = config('services.twilio')['number'];
        try {
            $client->messages->create(
                $to, // Text any number
                [
                    'from' => $twilioNumber, // From a Twilio number in your account
                    'body' => $messageBody,
                    'statusCallback' => $callbackUrl
                ]
            );
        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }
}
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Twilio\Rest\Client;

class TwilioRestClientProvider extends ServiceProvider
{
    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(
            Client::class, function ($app) {
                $accountSid = config('services.twilio')['accountSid'];
                $authToken = config('services.twilio')['authToken'];
                return new Client($accountSid, $authToken);
            }
        );
    }
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Order;
use Twilio\Rest\Client;
use Log;

class OrderController extends Controller
{
    public function index()
    {
        return view('index', ['orders' => Order::all()]);
    }

    public function show($id)
    {
        return view('show', ['order' => Order::find($id)]);
    }

    public function pickup(Client $client, Request $request, $id)
    {
        $order = Order::find($id);
        $order->status = 'Shipped';
        $order->notification_status = 'queued';
        $order->save();

        $callbackUrl = str_replace('/pickup', '', $request->url()) . '/notification/status/update';
        $this->sendMessage(
            $client,
            $order->phone_number,
            'Your laundry is done and on its way to you!',
            $callbackUrl
        );

        return redirect()->route('order.show', ['id' => $order->id]);
    }

    public function deliver(Client $client, Request $request, $id)
    {
        $order = Order::find($id);
        $order->status = 'Delivered';
        $order->notification_status = 'queued';
        $order->save();

        $callbackUrl = str_replace('/deliver', '', $request->url()) . '/notification/status/update';
        $this->sendMessage(
            $client,
            $order->phone_number,
            'Your laundry is arriving now.',
            $callbackUrl
        );

        return redirect()->route('order.index');
    }

    public function notificationStatus(Request $request, $id)
    {
        $order = Order::find($id);
        $order->notification_status = $request->input('MessageStatus');
        $order->save();
    }

    private function sendMessage($client, $to, $messageBody, $callbackUrl)
    {
        $twilioNumber = config('services.twilio')['number'];
        try {
            $client->messages->create(
                $to, // Text any number
                [
                    'from' => $twilioNumber, // From a Twilio number in your account
                    'body' => $messageBody,
                    'statusCallback' => $callbackUrl
                ]
            );
        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Order;
use Twilio\Rest\Client;
use Log;

class OrderController extends Controller
{
    public function index()
    {
        return view('index', ['orders' => Order::all()]);
    }

    public function show($id)
    {
        return view('show', ['order' => Order::find($id)]);
    }

    public function pickup(Client $client, Request $request, $id)
    {
        $order = Order::find($id);
        $order->status = 'Shipped';
        $order->notification_status = 'queued';
        $order->save();

        $callbackUrl = str_replace('/pickup', '', $request->url()) . '/notification/status/update';
        $this->sendMessage(
            $client,
            $order->phone_number,
            'Your laundry is done and on its way to you!',
            $callbackUrl
        );

        return redirect()->route('order.show', ['id' => $order->id]);
    }

    public function deliver(Client $client, Request $request, $id)
    {
        $order = Order::find($id);
        $order->status = 'Delivered';
        $order->notification_status = 'queued';
        $order->save();

        $callbackUrl = str_replace('/deliver', '', $request->url()) . '/notification/status/update';
        $this->sendMessage(
            $client,
            $order->phone_number,
            'Your laundry is arriving now.',
            $callbackUrl
        );

        return redirect()->route('order.index');
    }

    public function notificationStatus(Request $request, $id)
    {
        $order = Order::find($id);
        $order->notification_status = $request->input('MessageStatus');
        $order->save();
    }

    private function sendMessage($client, $to, $messageBody, $callbackUrl)
    {
        $twilioNumber = config('services.twilio')['number'];
        try {
            $client->messages->create(
                $to, // Text any number
                [
                    'from' => $twilioNumber, // From a Twilio number in your account
                    'body' => $messageBody,
                    'statusCallback' => $callbackUrl
                ]
            );
        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Order;
use Twilio\Rest\Client;
use Log;

class OrderController extends Controller
{
    public function index()
    {
        return view('index', ['orders' => Order::all()]);
    }

    public function show($id)
    {
        return view('show', ['order' => Order::find($id)]);
    }

    public function pickup(Client $client, Request $request, $id)
    {
        $order = Order::find($id);
        $order->status = 'Shipped';
        $order->notification_status = 'queued';
        $order->save();

        $callbackUrl = str_replace('/pickup', '', $request->url()) . '/notification/status/update';
        $this->sendMessage(
            $client,
            $order->phone_number,
            'Your laundry is done and on its way to you!',
            $callbackUrl
        );

        return redirect()->route('order.show', ['id' => $order->id]);
    }

    public function deliver(Client $client, Request $request, $id)
    {
        $order = Order::find($id);
        $order->status = 'Delivered';
        $order->notification_status = 'queued';
        $order->save();

        $callbackUrl = str_replace('/deliver', '', $request->url()) . '/notification/status/update';
        $this->sendMessage(
            $client,
            $order->phone_number,
            'Your laundry is arriving now.',
            $callbackUrl
        );

        return redirect()->route('order.index');
    }

    public function notificationStatus(Request $request, $id)
    {
        $order = Order::find($id);
        $order->notification_status = $request->input('MessageStatus');
        $order->save();
    }

    private function sendMessage($client, $to, $messageBody, $callbackUrl)
    {
        $twilioNumber = config('services.twilio')['number'];
        try {
            $client->messages->create(
                $to, // Text any number
                [
                    'from' => $twilioNumber, // From a Twilio number in your account
                    'body' => $messageBody,
                    'statusCallback' => $callbackUrl
                ]
            );
        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }
}