Laravel Twilio :: Send SMS, Receive SMS and Track SMS Status

Introduction

Twilio integrations have been a lot more common than I thought during the last few years. So I wanted to write the article I wish I have found the first time a had to do it.

Twilio’s SMS API is a flexible building block that can take you from sending your first text message all the way to sending and receiving millions.

In this article, I will talk about how to set up a Laravel application Twilio configuration and start sending SMS.

After that, we will dig deeper into how to keep track of the status of sent messages and keep a local updated representation of Twilio messages.

Finally, we will prepare the application to receive notifications every time your defined Twilio phone number gets a message and then store the received message locally.

Configuration and sending the first SMS

1. Get Twilio Credentials and Phone Number

Go to your Twilio dashboard at https://console.twilio.com/ and find your account SID, Auth Token, and Phone Number. If you don’t have a phone number yet you can create a trial phone number.

Keep in mind that a trial phone number can only send SMS to verified phones. This means you will have to add the phone number you intend to send messages to a verified numbers list.

2. Add Twilio Credentials to Laravel configuration.

.env fileTWILIO_SID=YOUR_ACCOUNT_SID
TWILIO_AUTH_TOKEN=YOUR_ACCOUNT_AUTH_TOKEN
TWILIO_FROM_NUMBER=YOUR_ACCOUNT_FROM_NUMBER(e.g. +1XXXXXXXXXX)
--------------------------------------------------------------/config/app.php'twilio' => [
'sid' => env('TWILIO_SID'),
'auth_token' => env('TWILIO_AUTH_TOKEN'),
'from_number' => env('TWILIO_FROM_NUMBER')
]

3. Install Twilio SDK for PHP

composer require twilio/sdk

4. Create Twilio Service Class

It is convenient to handle all Twilio SMS operations from a single class that is globally available. So every time you send a message you will do it through this service.

Remember to bind the service so it is globally accessible. In this case, we will create a provider CustomServiceProvider and bind TwilioService in the boot function.

php artisan make:provider CustomServiceProvider

And then add App\Providers\CustomServiceProvider::class into the $providers array on config/app.php.

Now the TwilioService should be available and we can test it from anywhere.

Also not required, but it will be convenient to add a new channel at /config/logging.php to log Twilio operations, at least for debugging.

...'twilio' => ['driver' => 'single','path' => storage_path('logs/twilio.log'),'level' => 'debug',],...

5. Create Twilio SMS Controller.

If you only care about sending messages then you don’t really need to create a controller. You can test and use the TwilioService sendMessage function from anywhere.

For this implementation, we will use this controller to provide a sendTest function. Later it will also be used to handle Twilio SMS Status Callback requests and Twilio SMS Received requests.

php artisan make:controller TwilioSmsController

Add testing endpoint to your API routes.

Route::any('/twilio/send-test',[TwilioSmsController::class, 'sendTest'])->name('twilio.send-test');

You are ready to make your first test. Hopefully, everything goes right on the first try. If not, you will get a debug error message.

Common errors you can get:

That’s it! 🎉 If you only need to send messages then you are ready to go.

If you want to keep track of the messages you sent and also be able to receive messages in your application then keep reading 👀.

Track Twilio SMS status changes

When we make a send SMS request to Twilio the message has to go through a sequence of statuses.

  • accepted
  • queued
  • sending
  • sent
  • delivery_unknown
  • delivered
  • undelivered
  • failed

The response we get from making a successful send SMS request will tell us that the message status is queued. This just means that Twilio accepted the request and it was added to the queue of messages to send, but we don’t know if it was actually sent.

If we want to keep track of an SMS status we have to provide a statusCallback parameter on each request. The statusCallback will be the URL to a webhook in our application that will be prepared to receive requests from Twilio.

1. Create TwilioSms Model and TwilioSmsLog Model.

TwilioSms will represent a Twilio SMS in our application and TwilioSmsLog will represent each event related to an SMS (status change, error, etc).

php artisan make:model TwilioSms -m
php artisan make:model TwilioSmsLog -m

Models:

Migrations:

2. Create status callback webhook route and implement Twilio Request Validation middleware.

Add new endpoint to api routes.

/**
* Twilio statusCallBack webhook
* is-twilio-request middleware makes sure only twilio has access to this route
*/
Route::any('/twilio/webhook/status-changed', [TwilioController::class, 'statusChanged'])->middleware(['is-twilio-request'])->name('api.twilio.status-changed');

Notice that we are using the middleware is-twilio-request. This route will be open to the public so we need to make sure that it only serves valid requests from Twilio. We can accomplish that by using the RequestValidator method provided by the Twilio PHP SDK.

Create Middleware

php artisan make:middleware TwilioRequestIsValid

Add the new middleware to app/Http/Kernel.php $routeMiddleware array.

/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
*
@var array
*/
protected $routeMiddleware = [
...
'is-twilio-request' => \App\Http\Middleware\TwilioRequestIsValid::class,
];

3. Update Twilio Service. Add status callback, create TwilioSmsModel on send and log.

The Twilio Service final version will be:

Now every time we make a sendMessage request the following will happen:

  • Add callback URL to sendMessage request.
  • If the request was successful create TwilioSms record on DB.
  • Create TwilioSmsLog.

Newly created messages.

New messages are created with status queued

And the log table.

Created log for send_sms event, associated with twilio_sms and current status

4. Handle Twilio Status Changed Request.

Add statusChanged Method to TwilioSmsController. The logic is just an example of implementation. You can make any adjustment as long as you return response([‘success’ => true], 200).

In summary, we log every request and if we can match the request with an SMS in our application and the request status is different from the current TwilioSms status we update it.

TwilioSmsController with status changed method:

With the webhook in place, we will start to see our messages go through different states. It usually just takes a few seconds to go from queued to sent.

It will look like this in the database.

That’s it for it tracking SMS status changes and keeping an updated representation of each Twilio SMS sent by the application. The next step is to receive and store messages.

Receive SMS

  1. Create messageReceived method on TwilioSmsController.

TwilioSmsController final version:

2. Add webhook route.

....../*** Twilio message received webhook* is-twilio-request middleware makes sure only twilio has access to this route*/Route::any('/twilio/webhook/message-received', [TwilioSmsController::class, 'messageReceived'])->middleware(['is-twilio-request'])->name('api.twilio.message-received');...
...

3. Add webhook URL to a phone number in Twilio console.

Find your phone number details page on the Twilio console.

Click on the phone number you want to manage and then find the Messaging section on the details page. Here you can define a “message comes in” action. Select webhook and input your webhook URL.

Save and you are all set to start receiving SMS in your application.

Whenever a message comes in we are going to create a TwilioSms record and a TwilioSmsLog record.

Conclusion

We accomplished:

✅ Send SMS.

✅ Track Sent SMS Status.

✅ Keep local updated representation of a Twilio SMS.

✅ Every time a Twilio phone receives a message we get it and store it.

Possible gotchas:

  • Test with phone numbers in E.164 formatting.
  • Trial phone numbers can only send messages to verified numbers.
  • Sometimes sending a message to a region is disabled, so you have to enable the region on the Twilio console.
  • Webhook URL must be publicly accessible. No auth. Not behind a server configuration restriction.

I hope this article was helpful.

If you have any feedback or found mistakes, please don’t hesitate to reach out to me.

--

--

--

Software Engineer. Laravel Enthusiast. Sharing my experiences with Laravel, Vue and anything web.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Carlos Santos

Carlos Santos

Software Engineer. Laravel Enthusiast. Sharing my experiences with Laravel, Vue and anything web.

More from Medium

Crazy Laravel Livewire made me easy to build my E-Commerce (Admin Panel and API) [Part 2]

Create Laravel JSON Fake REST APIs Server via JSON File

Laravel + Pusher without Echojs

Build Web App Mini Sosmed with Laravel 8 [Part 3]