Laravel and Twilio: Cloud communications in a nutshell

April 05, 2024 • 6 min read

Home / Blog / Laravel and Twilio: Cloud communications in a nutshell
Laravel and Twilio: Cloud communications in a nutshell | Learn how to send SMS using Laravel and Twilio in a step-by-step tutorial

Introduction

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.

 

Getting started

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.

 

Conclusion

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

221
Laravel,  Twilio

About the author

Author

Gonzalo Gomez

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.

Subscribe to my newsletter

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

Related posts

The ultimate guide to sending SMS using PHP and Twilio

September 16, 2024
IntroductionGoing with PHP and Twilio to send SMS is a powerful combination. Whether you're looking to build a webapp, a webservice, or even a script... Continue reading

Laravel Tip: Schema::getColumnListing method

May 15, 2024
IntroductionIn a previous post, I talked about the usage of Eloquent queries or the DB Facade to perform a mass insert of records, which you... Continue reading

Content rendering: Blade vs Vue and React

June 03, 2024
IntroductionThere are different approaches when it comes to showing data to the end user. Today, I'd like to talk about the 2 alternatives that I... Continue reading

Twilio: Create an A2P 10DLC Campaign

April 08, 2024
IntroductionIf your company requires to implement a messaging service to send, for example, SMS reminders for appointments, booking confirmations or any other message of any... Continue reading