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

977
Laravel,  Twilio
Published on April 05, 2024

Ready to automate your customer conversations?

Contact me

About the author

Author

Gonzalo Gomez

AI & Automation Specialist

I design AI-powered communication systems. My work focuses on voice agents, WhatsApp chatbots, AI assistants, and workflow automation built primarily on Twilio, n8n, and modern LLMs like OpenAI and Claude. Over the past 7 years, I've shipped 30+ automation projects handling 250k+ monthly interactions.

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

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

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

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

Traits and Laravel: the practical guide

April 18, 2024
IntroductionTraits are a mechanism for code re-use in single inheritance languages such as PHP. They were designed to allow us developers to save logic and... Continue reading