✉ Laravel Twilio SMS Implementation
Tags: laravel, twilio, php, webdev
Published: 2021-12-15 01:54:24


![Laravel Twilio SMS Implementation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/96ieis37fjz3de2iys7b.png)

## 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](https://www.twilio.com/docs/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.

&nbsp;
## Configuration and sending the first SMS 
&nbsp;
**1. Get Twilio Credentials and Phone Number**
![Twilio Credentials](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tv7ngvhp14v6q83ztzap.png)
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.

![Twilio Verified Numbers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i40dl4s2iguthtlpand2.png)

&nbsp;
**2. Add Twilio Credentials to Laravel configuration.**

.env file

```
TWILIO_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')
]
```

&nbsp;
**3. Install Twilio SDK for PHP**
`composer require twilio/sdk`

&nbsp;
**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.
{% gist https://gist.github.com/sanzoc/9a1443cb20e8804588511b6300b669cf %}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`
{% gist https://gist.github.com/sanzoc/ff314f481398dfa6152d0df719483d05 %}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',
],
...
```

&nbsp;
**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`

{% gist https://gist.github.com/sanzoc/f449cfa7209840b2f0824557e45a4bd5 %}

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:

- Invalid number. The provided To number must have [E.164](https://medium.com/r/?url=https%3A%2F%2Fwww.twilio.com%2Fdocs%2Fglossary%2Fwhat-e164) formatting.
- To phone number not verified. If you are using a trial phone number then you can only send SMS to [verified numbers](https://medium.com/r/?url=https%3A%2F%2Fsupport.twilio.com%2Fhc%2Fen-us%2Farticles%2F223180048-Adding-a-Verified-Phone-Number-or-Caller-ID-with-Twilio).
- [Permission to send an SMS has not been enabled for the region indicated by the 'To' number](https://medium.com/r/?url=https%3A%2F%2Fwww.twilio.com%2Fdocs%2Fapi%2Ferrors%2F21408%3Fdisplay%3Dembedded).

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 👀.


&nbsp;
---

## Track Twilio SMS status changes
When we make a send SMS request to Twilio the message has to go through a [sequence of statuses](https://medium.com/r/?url=https%3A%2F%2Fsupport.twilio.com%2Fhc%2Fen-us%2Farticles%2F223134347-What-are-the-Possible-SMS-and-MMS-Message-Statuses-and-What-do-They-Mean-).

- 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](https://medium.com/r/?url=https%3A%2F%2Fsupport.twilio.com%2Fhc%2Fen-us%2Farticles%2F360008989454-Tracking-the-Delivery-Status-of-an-Outbound-Twilio-SMS-or-MMS-Message) 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.

&nbsp;
**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
{% gist https://gist.github.com/sanzoc/0404133090ad29138f3fcfbd03d0fdac %}
{% gist https://gist.github.com/sanzoc/84c46c466b01b731f42d952ed9985474 %}

Migrations
{% gist https://gist.github.com/sanzoc/67f94bbf05b9b27671de6bd1a7c2ff50 %}
{% gist https://gist.github.com/sanzoc/d0ce9c2bcd23f23b781375a581770ffc %}


&nbsp;
**2. Create status callback webhook route and implement Twilio Request Validation middleware.**
Add new webhook url 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`
{% gist https://gist.github.com/sanzoc/82a59e0140f29bfbef4e7886081c2988 %}

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,
];
```

&nbsp;
**3. Update Twilio Service. Add status callback, create TwilioSmsModel on send and log.**

TwilioService final version:
{% gist https://gist.github.com/sanzoc/1e836b7017b51c87a12a5e1ce7f3a4e6 %}

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.

Created message.
![Created message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oufbeit656idc0ifjzy1.png)

Created log.
![Created log](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s5uuxsqcvp091spwjmkz.png)

&nbsp;
**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:
{% gist https://gist.github.com/sanzoc/d871450e30f6840c4c9e08defc55b752 %}

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.
![logs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2pm4v44trvmemsoazo42.png)

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.


&nbsp;
---


## Receive SMS 
&nbsp;
**1. Create messageReceived method on TwilioSmsController.**

TwilioSmsController final version:
{% gist https://gist.github.com/sanzoc/49a67724ce7d820db4866cc836c6ca4d %}

&nbsp;
**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');
...
...
```

&nbsp;
**3. Add webhook URL to a phone number in Twilio console.**

Find your phone number details page.
![console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i3jf4lii8wnyi04vpztz.png)

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.
![message webhook](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dfwgoqxy856esd6q7337.png)

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.
![sms](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xn3ypv9tjfzz4ueosee7.png)
![log](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g12u0dt0i8i5qmow0zrh.png)
 

&nbsp;
---


## 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.