5 de abril de 2024 • 6 min de lectura
Especialista en IA y Automatización
Diseño sistemas de comunicación impulsados por IA. Mi trabajo se centra en agentes de voz, chatbots de WhatsApp, asistentes de IA y automatización de flujos, construidos principalmente sobre Twilio, n8n y LLM modernos como OpenAI y Claude. En los últimos 7 años he entregado más de 30 proyectos de automatización que manejan más de 250 000 interacciones mensuales.
Si te gusta el contenido que hago, puedes suscribirte y recibir información valiosa por correo. No se envía spam, solo novedades sobre publicaciones interesantes o contenido especializado del que hablo.
En un capítulo anterior armé una guía con mis consejos para verificar tu número 10DLC en Twilio. Lo que nunca hice fue la guía de cómo enviar SMS con Laravel desde tu propia plataforma. Twilio ofrece implementaciones cortas en varios lenguajes para mandar el mensaje, y está bien como punto de partida. Yo quiero ir un paso más allá y mostrarte mi implementación en una app real que maneja alrededor de 3000 mensajes por día. Vamos a eso.
Nota: para enviar SMS desde un número 10DLC en producción, necesitás una campaña A2P 10DLC aprobada y activa. Mirá https://ggomez.dev/blog/twilio-create-an-a2p-10dlc-campaign para más información.
Además, si necesitás enviar SMS desde un número Toll-Free, vas a tener que pasar por la Toll-Free Message Verification dentro de Twilio. Mirá https://help.twilio.com/articles/5377174717595-Toll-Free-Message-Verification-for-US-Canada para más información.
Hay 2 formas de usar Twilio en nuestra app: a través de sus SDK o de su API. Esta guía usa el SDK, que ya se comunica con la API de Twilio por nosotros.
En tu aplicación de Laravel (o de PHP, para el caso), instalá el SDK de Twilio
composer require twilio/sdk
Después todo se reduce a usar el método send() del cliente de Twilio, que necesitamos instanciar dentro de nuestra app. A mí me gusta tener toda la lógica repartida en servicios y usar el patrón de Inyección de Dependencias cuando construyo en Laravel. Veamos entonces cómo usar el SDK de Twilio en nuestro servicio:
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()}");
}
}
}
Ese es nuestro servicio básico, y se puede extender para aprovechar más funcionalidades del SDK. Ahora podemos crear un servicio de SMS que va a interactuar con nuestro servicio de Twilio:
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;
}
}
}
La idea acá es desacoplar el SmsService del TwilioService. En algún momento quizás quieras cambiar de proveedor de comunicaciones en la nube, y te conviene minimizar el impacto en el código. En este ejemplo el SmsService es el único punto de contacto de la app. Podemos alternar entre Twilio y Vonage sin que el lado del cliente se entere de esos cambios.
Según tu arquitectura, quizás te convenga encolar los mensajes antes de mandárselos a Twilio, por ejemplo en batches. Yo no usé ese enfoque: prefiero que Twilio me encole los mensajes de su lado, porque no necesito que se entreguen al instante. Igual, el throughput (mensajes procesados por segundo) se define según tu campaña A2P 10DLC.
Eso es todo por ahora, si te sirvió, compartilo.
-Gonza
Descubre cuánto te está costando tu sistema de comunicaciones.
Obtén la auditoría de comunicacionesNo se encontraron resultados