Cómo crear un portal de SMS con Laravel y Twilio

March 22, 2019
Redactado por
Brian Iyoha
Colaborador
Las opiniones expresadas por los colaboradores de Twilio son propias.

xfZiGIwY7kMFumujtS99SsDUOzzE3HEluKNzI4Td5Ba7yorAqM9583sLeRzWPPuI2iDcAOjRAj2f0OZoGkCTjizc-ZoKWtWz98AHxpunO00UwgncR4v9XNn3bgJrLKPySdF2rbC0f2CuaGby8A

En algún punto del flujo de trabajo de la aplicación, es posible que debas transmitir información importante a tus usuarios. Gracias al avance de las tecnologías web, esto se puede hacer fácilmente con las notificaciones push. Sin embargo, la mayoría de estos servicios requieren que tus usuarios tengan conexión a Internet y, por desgracia, esto no sucede siempre. Afortunadamente, podemos superar esta dificultad mediante un sistema de notificaciones que no depende de Internet.

En este tutorial, te enseñaremos cómo utilizar los SMS programables de Twilio para crear un portal de notificaciones de SMS con Laravel. Una vez que hayamos terminado, habrás desarrollado un portal personalizado de notificaciones de SMS que te permitirá enviar notificaciones a tus usuarios a través de un panel de control con SMS.

Requisitos previos 

Para realizar este tutorial, necesitarás:

Configurar el proyecto

En primer lugar, debemos crear un nuevo proyecto de Laravel, ya sea con el instalador de Laravel o con Composer. En este tutorial, utilizaremos el instalador de Laravel. Si no lo tienes instalado, puedes consultar cómo hacerlo desde la documentación de Laravel. Para generar un proyecto nuevo de Laravel, ejecuta este comando en tu terminal:

$ laravel new sms-portal

Ahora, cambia tu directorio de trabajo a sms-portal e instala el SDK de Twilio a través de Composer:

$ cd sms-portal
$ composer require twilio/sdk 

Si no tienes instalado Composer en tu computadora, puedes hacerlo siguiendo las instrucciones que encuentras aquí.

Después de instalar el SDK de PHP de Twilio, necesitamos obtener nuestras credenciales de Twilio desde el panel de control de Twilio. Dirígete a tu Dashboard (Panel de control) y toma tu account_sid y tu auth_token.

Captura de pantalla de las credenciales de Twilio

Ahora, ve a la sección Phone Number (Número de teléfono) para obtener el número de teléfono con SMS activados.

Panel de número activo en Twilio

El siguiente paso es actualizar el archivo .env con nuestras credenciales de Twilio. Por lo tanto, abre el archivo .env que está ubicado en la raíz del directorio del proyecto y agrega estos valores:

TWILIO_SID="INSERT YOUR TWILIO SID HERE"
TWILIO_AUTH_TOKEN="INSERT YOUR TWILIO TOKEN HERE"
TWILIO_NUMBER="INSERT YOUR TWILIO NUMBER IN [E.164] FORMAT"

Configurar la base de datos

Ahora que tenemos un proyecto de Laravel básico con el SDK de Twilio instalado, vamos a crear nuestra base de datos. Si utilizas una aplicación de GUI como phpMyAdmin para administrar la base de datos, sigue adelante y crea una base de datos con el nombre sms_portal y omite esta sección. Si no cuentas con esa aplicación, utiliza una herramienta equivalente; y, si no tienes MySQL instalada, instálala desde el sitio oficial para tu plataforma.

Pon en marcha el terminal y ejecuta este comando para iniciar sesión en MySQL:

$ mysql -u {your_user_name}

NOTA: Agrega -p si tienes una contraseña para la instancia de MySQL.

Una vez que hayas iniciado sesión, ejecuta el siguiente comando para crear una nueva base de datos:

mysql> create database sms_portal;
mysql> exit;

Cambiemos la configuración de nuestra base de datos según corresponda en el archivo .env en la raíz de nuestra carpeta del proyecto.

DB_DATABASE=sms_portal
DB_USERNAME=root
DB_PASSWORD=

Crear migración

Ya que hemos creado nuestra base de datos, vamos a crear nuestramigración a la base de datos. Podemos hacer esto simplemente ejecutando este comando en nuestro terminal:

$ php artisan make:migration create_users_phone_number_table

Esto generará un archivo de migración {current_time_stamp}_create_users_phone_number_table en el directorio /database/migrations.

Ahora, abre la carpeta del proyecto en tu IDE o editor de texto favorito para que podamos comenzar a hacer cambios, según sea necesario. Abre el archivo de migración que acabamos de crear. Deberíamos tener el mismo contenido que este:

Creación de una migración en el IDE

Necesitamos agregar la columna phone_number a nuestra tabla. Podemos hacerlo mediante la edición del método up(), como se indica a continuación:

public function up() {
    Schema::create('users_phone_number', function (Blueprint $table) {
        $table->increments('id');
        $table->string('phone_number'); 
        $table->timestamps();
    });
}

Ahora tenemos nuestro archivo de migración listo para migrar. Podemos hacerlo simplemente ejecutando lo siguiente en nuestro terminal:

$ php artisan migrate

Debemos obtener un resultado de salida similar a este:

Los resultados de la consola de migración artesanal

Crear la interfaz de usuario

En este punto, tenemos nuestra configuración del proyecto. Es hora de crear una interfaz de usuario sencilla para agregar datos a nuestra base de datos y también para enviar notificaciones de SMS.

Abre el archivo /resources/views/welcome.blade.php y haz los siguientes cambios al bloque <head>.

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>SMS Portal With Twilio</title>
    <!-- Bootstrap styles CDN -->
     <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
</head>

Simplemente eliminamos el diseño inicial en la página y agregamos Bootstrap medianteBootstrapCDN para darle un estilo más sencillo. A continuación, vamos a crear dos formularios en la página. Uno para registrar el número de teléfono de los usuarios y otro para enviar mensajes de notificación personalizados a los usuarios seleccionados. Realiza los siguientes cambios en el bloque body.

<body>
    <div class="container">
        <div class="jumbotron">
            <div class="row">
                <div class="col">
                    <div class="card">
                        <div class="card-header">
                            Add Phone Number
                        </div>
                        <div class="card-body">
                            <form>
                                <div class="form-group">
                                    <label>Enter Phone Number</label>
             <input type="tel" class="form-control" placeholder="Enter Phone Number">
                                </div>
                  <button type="submit" class="btn btn-primary">Register User</button>
                            </form>
                        </div>
                    </div>
                </div>
                <div class="col">
                    <div class="card">
                        <div class="card-header">
                            Send SMS message
                        </div>
                        <div class="card-body">
                            <form>
                                <div class="form-group">
                                    <label>Select users to notify</label>
                                    <select multiple class="form-control">
                                        @foreach ($users as $user)
                                        <option>{{$user->phone_number}}</option>
                                        @endforeach
                                    </select>
                                </div>
                                <div class="form-group">
                                    <label>Notification Message</label>
                                 <textarea class="form-control" rows="3"></textarea>
                                </div>
             <button type="submit" class="btn btn-primary">Send Notification</button>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>

Si observas con más detalle el código anterior, notarás que tenemos lo siguiente:

      <select multiple class="form-control">
            @foreach ($users as $user)
              <option>{{$user->phone_number}}</option>
           @endforeach
      </select>

Este fragmento ayuda a generar un campo de opción para cada número de teléfono de usuario disponible devuelto con esta vista. Implementaremos esta característica en breve.  

Guardar el número de teléfono de los usuarios

En primer lugar, vamos a crear un modelo que usaremos para consultar y también insertar registros en la base de datos. Pon en marcha tu terminal en el directorio del proyecto y ejecuta lo siguiente:

 $ php artisan make:model UsersPhoneNumber

Abre el archivo creado en app/UsersPhoneNumber.php y agrega el siguiente código:

<?php
namespace App;

use Illuminate\Database\Eloquent\Model;

class UsersPhoneNumber extends Model
{
   protected $table= "users_phone_number";
   protected $fillable = [
        'phone_number'
    ];
}

NOTA: Estos son algunos aspectos que debes considerar.

  • Al agregar protected $table= "users_phone_number";, se le indica a Eloquent el nombre de la tabla para utilizar. Si esto no se proporciona, el nombre plural de la clase se utilizará como nombre de tabla. 
  • Agregar protected $fillable le indica a Eloquent que realice la asignación masiva de campos (Obtén más información en el enlace anterior).

A continuación, debemos crear el controlador donde implementaremos la lógica necesaria para cada ruta de solicitud. Una vez más, pon en marcha el terminal y ejecuta lo siguiente:

$ php artisan make:controller HomeController

Esto generará un archivo de controlador app/Http/Controllers/HomeController.php con el siguiente contenido:

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HomeController extends Controller
{
    //
}

Crear el método para guardar el número de teléfono

Creemos una función que genere una instancia de un nuevo modelo UsersPhoneNumber con los datos transferidos desde el cuerpo de la solicitud:

<?php

/**
 * Store a new user phone number.
 *
 * @param  Request  $request
 * @return Response
 */
public function storePhoneNumber(Request $request)
{
    //run validation on data sent in
    $validatedData = $request->validate([
        'phone_number' => 'required|unique:users_phone_number|numeric'
    ]);
    $user_phone_number_model = new UsersPhoneNumber($request->all());
    $user_phone_number_model->save();
    return back()->with(['success'=>"{$request->phone_number} registered"]);
}

A partir del código anterior, ejecutamos la validación de los datos que se transfieren desde el cuerpo de $request antes de proceder con la creación de una nueva instancia de UsersPhoneNumber. Después de guardar el número de teléfono del usuario, apareceremos nuevamente en la página de bienvenida con un mensaje de éxito que se muestra en la sesión.

Devolver la vista con los números de teléfono de los usuarios

Ahora tenemos que devolver la vista con los datos de los números de teléfono registrados. Escribiremos una función simple en el HomeController (Controlador de inicio) que consulta la tabla users_phone_number y devuelve los resultados de la consulta a la vista:

<?php

/**
 * Show the forms with users phone number details.
 *
 * @return Response
 */
public function show()
{
    $users = UsersPhoneNumber::all(); //query db with model
    return view('welcome', compact("users")); //return view with data
}

NOTA: compact() es una función PHP que te permite crear una matriz con nombres variables y sus valores.

Enviar mensajes con SMS programables de Twilio

En el siguiente paso, implementaremos el envío de SMS mediante la biblioteca de SMS programables de Twilio. Crea una función privada en el HomeController (Controlador de inicio) para que actúe como una función de ayuda para enviar mensajes:

<?php

/**
 * Sends sms to user using Twilio's programmable sms client
 * @param String $message Body of sms
 * @param Number $recipients string or array of phone number of recepient
 */
private function sendMessage($message, $recipients)
{
    $account_sid = getenv("TWILIO_SID");
    $auth_token = getenv("TWILIO_AUTH_TOKEN");
    $twilio_number = getenv("TWILIO_NUMBER");
    $client = new Client($account_sid, $auth_token);
    $client->messages->create($recipients, 
            ['from' => $twilio_number, 'body' => $message] );
}

La función recibe dos parámetros de $message y $recipients. Luego, obtendremos nuestras credenciales de Twilio almacenadas en las variables del entorno mediante la función de PHP integrada getenv(). Después de esto, crearemos un ejemplo de un nuevo cliente de Twilio con las credenciales. Ahora, podemos proceder a enviar el SMS mediante una llamada a:

$client->messages->create($recipients, [
    'from' => $twilio_number, 
    'body' => $message
]);

La función messages->create() de Twilio toma dos parámetros de un receptor del mensaje y una matriz con las propiedades de frombody en la que from es tu número de teléfono de Twilio activo.

Enviar una notificación al usuario cuando se registra

Hasta el momento, hemos completado nuestra función sendMessage(), que usaremos para enviar mensajes a los usuarios. Ahora, actualicemos nuestra función storePhoneNumber() para notificar a los usuarios cuando se registran correctamente. Para ello, realice los siguientes cambios a la función storePhoneNumber():

<?php

/**
 * Store a new user phone number.
 *
 * @param  Request  $request
 * @return Response
 */
public function storePhoneNumber(Request $request)
{
    //run validation on data sent in
    $validatedData = $request->validate([
        'phone_number' => 'required|unique:users_phone_number|numeric',
    ]);
    $user_phone_number_model = new UsersPhoneNumber($request->all());
    $user_phone_number_model->save();
    $this->sendMessage('User registration successful!!', $request->phone_number);
    return back()->with(['success' => "{$request->phone_number} registered"]);
}

Fantástico. Ahora, cada vez que se agrega el número de teléfono de un usuario a nuestra base de datos, podemos enviarle un mensaje de notificación para avisarle sobre la acción que se llevó a cabo.

Enviar notificaciones personalizadas

A continuación, escribamos una función para enviar mensajes personalizados a los usuarios seleccionados. Agrega el código siguiente a HomeController:

<?php 

/**
 * Send message to a selected users
 */
public function sendCustomMessage(Request $request)
{
    $validatedData = $request->validate([
        'users' => 'required|array',
        'body' => 'required',
    ]);
    $recipients = $validatedData["users"];
    // iterate over the array of recipients and send a twilio request for each
    foreach ($recipients as $recipient) {
        $this->sendMessage($validatedData["body"], $recipient);
    }
    return back()->with(['success' => "Messages on their way!"]);
}

Esta función pasa los datos validados del cuerpo $request en la variable $validatedData, permitiéndonos iterar sobre la matriz de $validatedData[users] y enviar a cada usuario el mensaje recibido de $validatedData["body"]. A continuación, nos redirigiremos a la página de bienvenida con un mensaje que se muestra en la sesión.

Crear rutas

Hemos creado correctamente las funciones del controlador. Ahora agreguemos nuestras rutasa la aplicación. Abre routes/web.phpy realiza los siguientes cambios:

<?php
Route::get('/', 'HomeController@show');
Route::post('/', 'HomeController@storePhoneNumber');
Route::post('/custom', 'HomeController@sendCustomMessage');

Actualizar el campo del formulario con rutas

Ahora dirígete a resources/views/welcome.blade.php y realiza los siguientes cambios en el campo del formulario:

//add the method attribute to the Register User form
// also add the name attributes to the input field 
<form method="POST">
    @csrf
    <div class="form-group">
        <label>Enter Phone Number</label>
        <input type="tel" class="form-control" name="phone_number" placeholder="Enter Phone Number">
    </div>
    <button type="submit" class="btn btn-primary">Register User</button>  
</form>

y

//add the method and action attributes to the Send custom message form
// also add the name attributes to the input fields 
<form method="POST" action="/custom">
    @csrf
    <div class="form-group">
        <label>Select users to notify</label>
        <select name="users[]" multiple class="form-control">
            @foreach ($users as $user)
            <option>{{$user->phone_number}}</option>
            @endforeach
        </select>
    </div>
    <div class="form-group">
        <label>Notification Message</label>
        <textarea name="body" class="form-control" rows="3"></textarea>
    </div>
    <button type="submit" class="btn btn-primary">Send Notification</button>
</form>

Prueba de nuestro código

Unamos todo el código que hemos hecho hasta ahora. En este punto, tu HomeController.php debe verse de la siguiente manera:

<?php
namespace App\Http\Controllers;

use App\UsersPhoneNumber;
use Illuminate\Http\Request;
use Twilio\Rest\Client;

class HomeController extends Controller
{
    /**
     * Show the forms with users phone number details.
     *
     * @return Response
     */
    public function show()
    {
        $users = UsersPhoneNumber::all();
        return view('welcome', compact("users"));
    }
    /**
     * Store a new user phone number.
     *
     * @param  Request  $request
     * @return Response
     */
    public function storePhoneNumber(Request $request)
    {
        //run validation on data sent in
        $validatedData = $request->validate([
            'phone_number' => 'required|unique:users_phone_number|numeric',
        ]);
        $user_phone_number_model = new UsersPhoneNumber($request->all());
        $user_phone_number_model->save();
        $this->sendMessage('User registration successful!!', $request->phone_number);
        return back()->with(['success' => "{$request->phone_number} registered"]);
    }
    /**
     * Send message to a selected users
     */
    public function sendCustomMessage(Request $request)
    {
        $validatedData = $request->validate([
            'users' => 'required|array',
            'body' => 'required',
        ]);
        $recipients = $validatedData["users"];
        // iterate over the array of recipients and send a twilio request for each
        foreach ($recipients as $recipient) {
            $this->sendMessage($validatedData["body"], $recipient);
        }
        return back()->with(['success' => "Messages on their way!"]);
    }
    /**
     * Sends sms to user using Twilio's programmable sms client
     * @param String $message Body of sms
     * @param Number $recipients Number of recipient
     */
    private function sendMessage($message, $recipients)
    {
        $account_sid = getenv("TWILIO_SID");
        $auth_token = getenv("TWILIO_AUTH_TOKEN");
        $twilio_number = getenv("TWILIO_NUMBER");
        $client = new Client($account_sid, $auth_token);
        $client->messages->create($recipients, ['from' => $twilio_number, 'body' => $message]);
    }
}

y, para nuestra vista, tu welcome.blade.php debe verse así:

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>SMS Portal With Twilio</title>
    <!-- Styles -->
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS"
        crossorigin="anonymous">
</head>
<body>
    <div class="container">
        <div class="jumbotron">
            @if (session('success'))
            <div class="alert alert-success">
                {{ session('success') }}
            </div>
            @endif
            @if ($errors->any())
            <div class="alert alert-danger">
                <ul>
                    @foreach ($errors->all() as $error)
                    <li>{{ $error }}</li>
                    @endforeach
                </ul>
            </div>
            @endif
            <div class="row">
                <div class="col">
                    <div class="card">
                        <div class="card-header">
                            Add Phone Number
                        </div>
                        <div class="card-body">
                            <form method="POST">
                                @csrf
                                <div class="form-group">
                                    <label>Enter Phone Number</label>
                                    <input type="tel" class="form-control" name="phone_number" placeholder="Enter Phone Number">
                                </div>
                                <button type="submit" class="btn btn-primary">Register User</button>
                            </form>
                        </div>
                    </div>
                </div>
                <div class="col">
                    <div class="card">
                        <div class="card-header">
                            Send SMS message
                        </div>
                        <div class="card-body">
                            <form method="POST" action="/custom">
                                @csrf
                                <div class="form-group">
                                    <label>Select users to notify</label>
                                    <select name="users[]" multiple class="form-control">
                                        @foreach ($users as $user)
                                        <option>{{$user->phone_number}}</option>
                                        @endforeach
                                    </select>
                                </div>
                                <div class="form-group">
                                    <label>Notification Message</label>
                                    <textarea name="body" class="form-control" rows="3"></textarea>
                                </div>
                                <button type="submit" class="btn btn-primary">Send Notification</button>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

Si tu código es el mismo, está todo listo para continuar. Si no es así, vuelve a revisar el código para encontrar lo que falta.

Ejecutar nuestra aplicación

Abre el terminal, ve al directorio del proyecto y ejecuta el siguiente comando:

$ php artisan serve

Esto le servirá a tu aplicación de Laravel en un puerto con host local, normalmente 8000. Abre el enlace del host local impreso después de ejecutar el comando en el navegador. Debería aparecer una página como esta:

App de Laravel que se ejecuta en el host local

Continúa y registra un nuevo número de teléfono. Si todo sale bien, deberías recibir un mensaje SMS en breve en el que se te notifica acerca del registro.

Ejemplo de mensaje de texto de la cuenta de prueba de Twilio

 También puedes probar el envío de notificaciones personalizadas mediante la selección de un usuario en el campo de selección y la inserción del texto que se enviará a los usuarios seleccionados en el área de texto. Cuando hayas terminado, haz clic en el botón Send Notification (Enviar notificación) y recibirás un SMS con el mensaje de notificación personalizado.

Notificaciones personalizadas

Conclusión

Ahora que completaste este tutorial, debes poder integrar los SMS programables de Twilio en tu aplicación de Laravel y enviar mensajes de notificación a través de SMS. Si deseas ver el código fuente completo de este tutorial, puedes encontrarlo en Github

Puedes avanzar en esto aún más si permites que los usuarios realicen acciones a partir de las notificaciones de SMS que reciben. 

Me encantaría responder cualquier pregunta que puedas tener respecto a este tutorial. Puedes contactarme a través de los siguientes medios: