April 05, 2024 • 6 min read
In a previous chapter, I added a small guide with my tips on how to get your 10DLC phone number verified in Twilio, but I never made the guide on how to wrap everything up to send SMS from within your platform. That being said, Twilio itself offers some small code implementations on different programming languages in order to send the SMS. I want to go one step further and share my code implementation for a real-world app that handles sending around 3000 messages/day. Let's get to it.
Note: in order to send SMS from a 10DLC number in a production environment, you need to have an approved and active A2P 10DLC campaign. Please refer to https://ggomez.dev/blog/twilio-create-an-a2p-10dlc-campaign for more information.
Additionally, if you need to send SMS from a Toll-Free number, you'll need to go through the Toll-Free Message Verification inside Twilio. Please refer to https://help.twilio.com/articles/5377174717595-Toll-Free-Message-Verification-for-US-Canada for more information.
There are 2 possible ways of using Twilio in our app: through their SDK's or their API. This guide will make use of the SDK since it already communicates with the Twilio API for us.
In your Laravel (or PHP for that matter) application, install the Twilio SDK
composer require twilio/sdk
Next, it's all about making use of the send()
method of the Twilio client that we need to instanciate from within our app. Personally, I like to have all my logic across services and make use of the Dependency Injection pattern when building in Laravel, so let's see how we can use the Twilio SDK in our service:
app\Services\TwilioMessageService.php
<?php
namespace App\Services;
use App\Models\TwilioSmsLog;
use Error;
use Exception;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
use Twilio\Rest\Client;
class TwilioMessageService
{
public function send(Model $model, string $message)
{
$client = new Client(config('services.twilio.account_sid'), config('services.twilio.auth_token'));
$twilioMessage = $client->messages->create(
$model->PhoneNumberE164,
array(
'from' => config('services.twilio.from'),
'body' => $message
)
);
$this->saveLogs($twilioMessage);
}
protected function saveLogs(mixed $message)
{
try {
if (!$message instanceof MessageInstance) {
throw new Error("The message is not an instance of MessageInstance: {$message}");
}
TwilioSmsLog::create([
'sid' => $message->sid,
'body' => $message->body,
'direction' => $message->direction,
'from' => $message->from,
'to' => $message->to,
'dateCreated' => $message->dateCreated,
'dateUpdated' => $message->dateUpdated,
'price' => $message->price,
'accountSid' => $message->accountSid,
'status' => $message->status,
'priceUnit' => $message->priceUnit,
'apiVersion' => $message->apiVersion
]);
} catch (Exception $ex) {
Log::error("Something went wrong storing the SMS logs: {$ex->getMessage()} at {$ex->getLine()}");
}
}
}
That's our basic service, which can be extended to use even more functionalities that the SDK can provide to us. Next, we can create an SMS service, which is going to interact with our Twilio service:
app\Services\SmsService.php
<?php
namespace App\Services;
use App\Models\AppointmentMessage;
use App\Services\TwilioMessageService;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Blade;
class SmsService
{
public TwilioMessageService $twilioMessageService;
public VonageService $vonageService;
public function __construct(TwilioMessageService $twilioMessageService, VonageService $vonageService)
{
$this->twilioMessageService = $twilioMessageService;
$this->vonageService = $vonageService;
}
/**
* Send an SMS to either a customer or a lead model.
* The type of message to send is determined by the type parameter.
*
* @return void
*/
public function send(string $type, Model $model)
{
$appointmentMessage = AppointmentMessage::where('type', $type)->first();
if ($appointmentMessage) {
$data = $this->mapMessageVariables($type, $model);
$message = Blade::render($appointmentMessage->message, $data, true);
$this->twilioMessageService->send($model, $message);
}
}
/**
* Send a raw message to a customer or a lead model.
*
* @return void
*/
public function sendRaw(Model $model, string $message)
{
$this->twilioMessageService->send($model, $message);
}
/**
* Map the message variables to send in the SMS.
*
* @return array
*/
protected function mapMessageVariables(string $type, Model $model): array
{
switch ($type) {
case 'created':
return [
'clientName' => "{$model->Name} {$model->Surname}",
'date' => date('m/d/Y', strtotime($model->AppointmentDate)),
'time' => Carbon::createFromFormat('H:i:s', $model->AppointmentTime)->format('g:iA'),
'timezone' => $model->office->availabilityProfile->Timezone,
'officeAddress' => $model->office->address,
'officePhoneNumber' => $model->office->PhoneNumber
];
break;
case 'updated':
return [
'clientName' => "{$model->Name} {$model->Surname}",
'date' => date('m/d/Y', strtotime($model->AppointmentDate)),
'time' => Carbon::createFromFormat('H:i:s', $model->AppointmentTime)->format('g:iA'),
'timezone' => $model->office->availabilityProfile->Timezone,
'officeAddress' => $model->office->address,
'officePhoneNumber' => $model->office->PhoneNumber
];
break;
case 'cancelled':
return [
'clientName' => "{$model->Name} {$model->Surname}",
'date' => date('m/d/Y', strtotime($model->AppointmentDate)),
'timezone' => $model->office->availabilityProfile->Timezone,
'officePhoneNumber' => $model->office->PhoneNumber,
'time' => Carbon::createFromFormat('H:i:s', $model->AppointmentTime)->format('g:iA'),
];
break;
case 'existing_client':
return [
'officeName' => $model->office->name,
'officePhoneNumber' => $model->office->PhoneNumber
];
break;
default:
return [];
break;
}
}
}
The idea here is to decouple the SmsService from the TwilioService because you might want to change your cloud communications provider in the future while minimizing the code impact. In this example, by using the SmsService as a single point of contact in our app, we can swap between the Twilio and Vonage service to send the SMS without the client side knowing about such implementation and changes.
It is also important to note that, depending on your architecture, you might want to queue your messages before sending them to Twilio in, for example, batches. I personally did not use that approach because I rather Twilio queue up the messages for me on their end, as I do not have a need for messages to be instantly delivered, althought the throughput is defined based on your A2P 10DLC campaign.
That's all for now, if you found the implementation helpful, feel free to share!
-Gonza
Sr. Software Engineer
I have over 6 years of experience building highly scalable web applications using a wide variety of technologies. Currently focusing on Laravel, Livewire, Vue.js and AWS as my go-to stack.
If you enjoy the content that I make, you can subscribe and receive insightful information through email. No spam is going to be sent, just updates about interesting posts or specialized content that I talk about.
Ready to take your project to the next level?
Contact me