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

580
Laravel,  Twilio
Published on April 05, 2024

About the author

Author

Gonzalo Gomez

Sr. Software Engineer

Senior software engineer located in Buenos Aires, Argentina. I specialize in building highly scalable web applications and I've been delivering MVPs and helping companies with their digital transformation for the past 7 years. I am proficient in a variety of technologies, including Laravel, Vue.js, Twilio, AWS, React.js, Node.js and MySQL.

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.

Related posts

Laravel 12, Laravel Cloud and Starter Kits

February 27, 2025
Introducing Laravel 12: Enhancements and New Features for Web Developers Laravel 12, the latest iteration of the popular PHP framework, has been released with a focus... Continue reading

Eloquent queries vs DB Facade

May 01, 2024
IntroductionIn Laravel development, making the right choices regarding database operations can significantly impact application performance and scalability. When dealing with tasks such as bulk data... 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

How to Stop Answering the Same Customer Questions 5x a Day (Without Hiring More Agents)

May 12, 2025
IntroductionYour team is answering the same exact questions every single day: “What’s your address?”“Are you open on holidays?”“What’s the return policy?”“Do you offer WhatsApp support?” Whether it’s... Continue reading