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
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 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')
]
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:
- Invalid number. The provided To number must have E.164 formatting.
- To phone number not verified. If you are using a trial phone number then you can only send SMS to verified numbers.
- Permission to send an SMS has not been enabled for the region indicated by the 'To' number.
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 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
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.
TwilioService final version:
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.
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.
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.
Top comments (0)