https://d226lax1qjow5r.cloudfront.net/blog/blogposts/send-sms-from-php-with-failover-dr/sms-cupcake.png

Envío de SMS desde PHP con Failover: La pastelería Cupcake

Publicado el May 4, 2021

Tiempo de lectura: 11 minutos

Mantenerse en contacto con los clientes es fundamental para cualquier organización. En este artículo, exploraremos la Mensajes API de Vonage y cómo puede usarse para garantizar que una empresa pueda enviar mensajes a sus clientes de la manera que más les convenga. El caso de estudio es un negocio imaginario de "pastelería de cupcakes" que necesita la capacidad de enviar mensajes a sus clientes. Este post muestra cómo utilizar la piedra angular de la comunicación moderna, los SMS, en tu aplicación web PHP. También demuestra cómo el Messages API puede utilizar otros canales de comunicación para enviar su mensaje si su primer intento falla, para darle la mejor oportunidad de llegar a su cliente.

Si eres un desarrollador web preparado para implementar modernas funciones de mensajería en tu aplicación, ¡este tutorial es para ti!

Preparar los ingredientes

Antes de empezar, necesitaremos reunir los siguientes ingredientes:

  • PHP en un servidor web de acceso público, o PHP en una plataforma de desarrollo con una herramienta como ngrok para que tu sitio web esté disponible. Vonage envía respuestas mediante webhook por lo que necesita poder llegar a tu aplicación.

  • CLI de Vonage si aún no la tienes.

  • Es más divertido si tienes dos números de teléfono a los que enviar SMS :)

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

This tutorial also uses a virtual phone number. To purchase one, go to Numbers > Buy Numbers and search for one that meets your needs.

Conozca la Messages API

Vonage es conocido desde hace mucho tiempo por sus capacidades de SMS, pero Messages API es nueva (aún está en Beta). Con Messages API puedes enviar mensajes a varios canales diferentes usando una sola interfaz. A menudo es más barato enviar un mensaje a Facebook Messenger o WhatsApp que a un SMS, y Messages API significa que sólo necesitas integrar una herramienta para cubrirlos todos. Además, con el tiempo se irán añadiendo más canales de mensajes, por lo que es una inversión que te ahorra tener que añadir más integraciones para cada nueva plataforma de mensajería que se ponga de moda.

La API de envío complementa a la API de Messages API y aporta más fiabilidad a la tarea de hacer llegar el mensaje a su destinatario. Con la Dispatch API, puedes establecer reglas sobre qué hacer si un mensaje no se entrega en un plazo de tiempo determinado, y también decir qué hacer a continuación. Así, si tienes otro método de contacto para ese usuario, un número de teléfono alternativo, por ejemplo, o has interactuado con él en Facebook, puedes enviarle un segundo mensaje a través de otro método. Esta entrada muestra un ejemplo de envío a un número de teléfono alternativo, una función que a menudo deseo para mí cuando utilizo números diferentes en distintos países cuando viajo.

Configure sus puntos finales Webhook

Visite la sección Mensajes y Despacho para crear una aplicación y configurar los webhooks que utilizaremos en este proyecto:

  • La URL de estado debería ser [YOUR URL HERE]/status por ejemplo con ngrok, sería algo como https://abcdef1.ngrok.io/status.

  • La URL del mensaje entrante debe ser [YOUR URL HERE]/inbound por ejemplo con ngrok, sería algo como https://abcdef1.ngrok.io/inbound.

Estas dos rutas /status y /inboundpueden llamarse como quieras, pero los ejemplos aquí coinciden con lo que hay en el código de ejemplo que usarás desde GitHub en un momento...

En el panel de control, anota el ID de la aplicación que has creado y asegúrate de que también tienes el archivo de la clave privada (hay un práctico botón para crear una clave que generará un par de claves pública/privada, poniendo la pública en la configuración de tu aplicación y descargando la privada para que la utilices con tu aplicación).

Precalentar el Código

(¿estamos llevando esta metáfora pastelera demasiado lejos? ¡Lo siento!)

El código de una aplicación funcional está disponible en GitHub. Visite https://github.com/nexmo-community/bakery-messaging-with-dispatch y clone el repositorio o descargue el código.

Una vez que lo tengas, hay algunas dependencias que necesitamos instalar. Para mantener las cosas lo más simple posible, este proyecto utiliza el microframework Slim. Para hacer las llamadas a la API (ya que las APIs de Messaging y Dispatch están todavía en Beta, no están soportadas en nuestra biblioteca PHP todavía), el proyecto utiliza GuzzleHTTP.

Para instalar las dependencias, utilice Compositor:

composer install

La Messages API y la Dispatch API utilizan tokens web JSON (JWT) para la autenticación. Toma el ID de aplicación que creaste en el panel y úsalo con la CLI de Vonage para ejecutar un comando como este (suponiendo que tu clave privada se llama private.key):

vonage jwt --application_id=VONAGE_APPLICATION_ID

El resultado de este comando es su JWT que utilizará para acceder con esta aplicación; cópielo ahora en su portapapeles. Tenga en cuenta que caduca cada 24 horas, por lo que puede que tenga que repetir este proceso cuando su aplicación, que funciona perfectamente, de repente empiece a devolver errores de "Token no válido".

La aplicación necesita el JWT, el ID de la aplicación y también algunos datos de contacto para los mensajes que enviará. Hay una plantilla de configuración en config.php.sampleCopie este archivo y llámelo config.phpy edite los valores para adaptarlos a su plataforma. Usted necesitará:

  • El ID de la aplicación de nuevo.

  • El JWT que acaba de generar.

  • El número de teléfono para enviar mensajes desde.

  • Dos Numbers de teléfono para enviar mensajes a.

Después de esto, los ingredientes están listos y ¡podemos empezar a hacer algo increíble!

Cocine una excelente comunicación con el cliente

Llegados a este punto, los preparativos han concluido y la aplicación está lista para ser utilizada. Configure su servidor web con el directorio public/ como webroot. Estoy usando una plataforma de desarrollo local con ngrok así que mis comandos de configuración están php -S localhost:8080 public/index.php en un terminal y ngrok http 8080 en otro.

Si Ngrok te da una nueva URL (no puedes reservar URLs en una cuenta gratuita), entonces no olvides volver al panel de control y actualizar esas URLs de webhooks

Ahora vamos a cargar la página de inicio del proyecto. Deberías ver un formulario muy simple para enviar un mensaje. Antes de hacerlo, tomemos un momento para ver cómo funcionará.

Comprender el proceso de la API de Messages

El proceso de envío de un mensaje con la Messages API es el siguiente:

  1. Nosotros escribimos el mensaje. Esto va en el formulario de entrada que puedes ver en la página de inicio.

  2. Enviamos los datos del número de teléfono del que procede el mensaje deel número del que procede a y el propio mensaje, en formato JSON. Consulte la Documentación de la API para obtener información detallada sobre lo que puede enviar aquí.

  3. La respuesta para un mensaje correcto es un código de estado 202, que significa "Aceptado" y un campo message_uuid en el cuerpo JSON de la respuesta.

  4. Toda la comunicación posterior de Nexmo se realiza a través de POST webhooks de solicitud al /status de nuestra aplicación. Cada solicitud entrante incluirá el ID del mensaje al que se refiere el estado y una marca de tiempo. Todo ello en formato JSON.

  5. Un webhook de estado indica que el mensaje se ha enviado. Hay más información sobre los estados de los mensajes en la documentación de la API.

  6. Otro webhook de estado indica que el mensaje fue entregado (si lo fue) y cuánto costó.

Está claro que es muy importante que podamos leer las actualizaciones de estado, así que veamos primero el código para ello.

Manejar las actualizaciones de estado

Cuando ocurre algo interesante en relación con el estado del mensaje, se envía un webhook al webhook que configuraste en el dashboard al principio. Para esta aplicación es /status. Aquí está el código para esa ruta:

$app->post('/status', function (Request $request, Response $response) {
    error_log($request->getBody());
    print_r($request->getParsedBody());
});

En este ejemplo de aplicación no hace mucho, pero captura todas las respuestas y las pone en los logs de tu servidor. También les salidas, que puede ser útil más adelante cuando la depuración de algunas características más avanzadas.

Envíe su mensaje al cliente

Screenshot showing a custom message to be sentCupcake Bakery Custom Message

En este punto, siéntete libre de escribir un mensaje en la casilla. El mío dice "Tus magdalenas están listas para recoger", porque siempre me alegra recibir este mensaje. Pulsa enviar y el mensaje debería llegar rápidamente a tu teléfono. ¿Ha sido mágico? No, veamos el código.

$app->map(['GET', 'POST'], '/', function (Request $request, Response $response, array $args) {
    $config = $this->get('config');
    $information = [];
    $title = "Cupcake Bakery Customer Messaging";

    if($data = $request->getParsedBody()) {
        $message = $data['message'];
        $client = new \GuzzleHttp\Client(['base_uri' => "https://api.nexmo.com/v0.1/messages"]);

        try {
            $apiResponse = $client->request('POST', '/v0.1/messages', [
                'headers' => [
                    'Authorization' => 'Bearer ' . $config['jwt'],
                    'Content-Type' => 'application/json',
                    'Accept' => 'application/json'
                ],
                'json' => [
                    'from' => $config['from'],
                    'to' => $config['customer1'][0],
                    'message' => [
                        'content' => [
                            'type' => 'text',
                            'text' => $message
                        ]
                    ]
                ]
            ]);

            $information['statusCode'] = $apiResponse->getStatusCode();
            $information['body'] = $apiResponse->getBody();
        } catch (Exception $e) {
            $response = $e->getResponse();
            $responseBodyAsString = $response->getBody()->getContents();
            echo $responseBodyAsString;
            error_log($responseBodyAsString);
        }

    }

    $response = $this->view->render($response, 'index.html', ['information' => $information, 'title' => $title]);
    return $response;
});

Esta ruta está disponible tanto para GET y POST porque el formulario se carga primero con GET y después, cuando se envía, se utiliza POST. Si hay POST entonces los datos del mensaje se utilizan junto con la configuración que estableciste anteriormente para construir una solicitud de API. El código de estado de la respuesta y el cuerpo son capturados y enviados a la página también, ya que esto puede ser útil cuando se trabaja con esta aplicación de demostración o adaptarlo para construir su propio. Usando esa salida de depuración, puedes obtener el código de estado message_uuid del mensaje que acabas de enviar.

Este ejemplo utiliza el Slim Framework, pero la mayor parte del código no es específico de Slim y funcionaría en cualquier aplicación PHP. Para saber más sobre Slim, visite https://www.slimframework.com/ - Recomiendo especialmente su "tutorial "Primera aplicación.

Seguimiento del progreso de los mensajes

Dado que el /status ya está configurado para recibir los webhooks, puede ir allí para comprobar lo que está sucediendo. Si ha enviado más de un mensaje, el ID del mensaje es aún más importante, pero en esta etapa, es probable que sea obvio a qué mensaje se refieren las actualizaciones de estado.

Si el mensaje se envía correctamente, verá dos actualizaciones de estado. La primera confirma que el mensaje se ha enviado:

{
    "message_uuid": "a5587e33-c304-4bf9-85a3-823e379e8a68",
    "to": {
        "number": "447700900001",
        "type": "sms"
    },
    "from": {
        "number": "	447700900000",
        "type": "sms"
    },
    "timestamp": "2018-10-17T10:17:02.889Z",
    "status": "submitted"
}

Después de que el mensaje llega a mi teléfono, recibo una segunda actualización de estado que muestra información sobre la entrega del mensaje:

{
    "message_uuid": "a5587e33-c304-4bf9-85a3-823e379e8a68",
    "to": {
        "number": "447700900001",
        "type": "sms"
    },
    "from": {
        "number": "	447700900000",
        "type": "sms"
    },
    "timestamp": "2018-10-17T10:17:05.480Z",
    "status": "delivered",
    "usage": {
        "price": "0.0333",
        "currency": "EUR"
    }
}

Otros canales de mensajería, como Facebook Messenger, también pueden devolver un estado "leído" para que sepas que el usuario ha visto realmente el mensaje.

También recibirá actualizaciones de estado si hay errores con su mensaje. En este caso, habrá un campo errors campo en el nivel superior de los datos JSON y detalles de su error incluyendo un código y una razón para el error. Mantenga un ojo en el /status a medida que trabaje con la Messages API, ya que contiene mucha información clave que le ayudará a desarrollar sus propias aplicaciones.

Utilizar datos de contacto alternativos si no se recibe el mensaje

Esto es comunicación con el cliente al siguiente nivel: si el mensaje no llega al usuario, detéctalo y prueba con otros datos de contacto que tengas de ese usuario. Lo bueno de esto es que, aunque los usuarios suelen preferir WhatsApp o Facebook Messenger (y puede resultar más barato enviarlos), los SMS llegan a la gente de forma más fiable aunque se hayan quedado sin datos o estén en una zona con poca señal. Como desarrolladores, no tenemos que esforzarnos en detectar los estados de los mensajes, añadir lógica de reintento o crear código que pueda gestionar muchas plataformas de mensajería diferentes. Dispatch API hace todo esto por nosotros, así que es muy, muy fácil.

Consulte la Documentación de la Dispatch API para más información aparte de este ejemplo concreto

La aplicación de ejemplo utiliza un número SMS alternativo (algo que a menudo encuentro útil cuando viajo y un paquete telefónico funciona mejor que otro en algunos lugares), pero podrías configurar múltiples detalles "desde" y admitir cualquier número de métodos de contacto diferentes para un usuario exactamente de la misma manera que en el ejemplo "prueba otro SMS" que se muestra aquí.

Controle la Dispatch API

Para añadir este nivel adicional, se utiliza la Dispatch API sobre la Messages API. En la aplicación de ejemplo puedes ver esto en acción en la acción /message-with-dispatch que está detrás del enlace "Enviar mensaje con fallback" que has visto en la interfaz web de la aplicación de ejemplo. El código no cambia mucho pero la petición que enviamos tiene algunas diferencias:

    $apiResponse = $client->request('POST', '/v0.1/dispatch', [
        'headers' => [
            'Authorization' => 'Bearer ' . $config['jwt'],
            'Content-Type' => 'application/json',
            'Accept' => 'application/json'
        ],
        'json' => [
            'template' => 'failover',
            'workflow' => [
                [
                    'from' => $config['from'],
                    'to' => $config['customer1'][0],
                    'message' => [
                        'content' => [
                            'type' => 'text',
                            'text' => $message
                        ]
                    ],
                    'failover' => [
                        'expiry_time' => 15, // in seconds, 15 is the minimum
                        'condition_status' => 'delivered'
                    ]
                ],
                [
                    'from' => $config['from'],
                    'to' => $config['customer1'][1],
                    'message' => [
                        'content' => [
                            'type' => 'text',
                            'text' => 'Message retry. ' . $message
                        ]
                    ]
                ]
            ]
        ]
    ]);

La estructura ha cambiado, ahora especificamos un template en el nivel superior y luego un workflow que es una matriz de métodos y detalles de contacto con algunos criterios para que la plantilla los utilice. Este ejemplo utiliza la plantilla failover (actualmente la única opción) para garantizar que si el SMS no se entrega en 15 segundos (es sólo una demostración, ¿cuánto tiempo quieres esperar?), enviamos otro mensaje al otro número que tenemos para ese cliente.

Siga las actualizaciones del estado de los envíos

Exactamente igual que la Messages API, la Dispatch API simplemente devuelve un código de estado de 202 Accepted y un código dispatch_uuid para que podamos vincular los eventos relacionados con esta solicitud de API.

Al igual que en el ejemplo anterior en el que se utilizó la API de Messages, recibirá actualizaciones cuando se envíe el mensaje, así como cuando se entregue/lea o cuando cambie el estado de envío. Aquí está la secuencia de datos que veo cuando Dispatch API no puede entregar al primer número de teléfono y cae de nuevo a la segunda.

En primer lugar: envíe el primer mensaje al primer número.

{
    "message_uuid": "afb5f546-97e7-44ba-97cd-bb7706a93f4e",
    "to": {
        "number": "447700900001",
        "type": "sms"
    },
    "from": {
        "number": "	447700900000",
        "type": "sms"
    },
    "timestamp": "2018-10-19T11:33:07.118Z",
    "status": "submitted",
    "_links": {
        "dispatch": {
            "href": "v0.1/dispatch/de8d9eaf-8d10-407f-840b-53473f26c173",
            "dispatch_uuid": "de8d9eaf-8d10-407f-840b-53473f26c173"
        }
    }
}

Cuando no puede entregarlo (hago la prueba poniendo el "primer" teléfono en modo avión), vuelve a llegar exactamente el mismo estado pero con el "segundo" número de teléfono en los datos:

{
    "message_uuid": "e083fc2e-dffc-42c9-a7b3-446ee5fe67ba",
    "to": {
        "number": "447700900002",
        "type": "sms"
    },
    "from": {
        "number": "	447700900000",
        "type": "sms"
    },
    "timestamp": "2018-10-19T11:33:25.071Z",
    "status": "submitted",
    "_links": {
        "dispatch": {
            "href": "v0.1/dispatch/de8d9eaf-8d10-407f-840b-53473f26c173",
            "dispatch_uuid": "de8d9eaf-8d10-407f-840b-53473f26c173"
        }
    }
}

El segundo mensaje ha sido entregado.

{
    "message_uuid": "e083fc2e-dffc-42c9-a7b3-446ee5fe67ba",
    "to": {
        "number": "447700900002",
        "type": "sms"
    },
    "from": {
        "number": "	447700900000",
        "type": "sms"
    },
    "timestamp": "2018-10-19T11:33:27.385Z",
    "status": "delivered",
    "usage": {
        "price": "0.0333",
        "currency": "EUR"
    },
    "_links": {
        "dispatch": {
            "href": "v0.1/dispatch/de8d9eaf-8d10-407f-840b-53473f26c173",
            "dispatch_uuid": "de8d9eaf-8d10-407f-840b-53473f26c173"
        }
    }
}

Si el primer mensaje también se entrega en algún momento posterior, obtendrá una actualización de estado muy similar para eso, no voy a pegar que también. Normalmente lo recibo unos diez minutos después, cuando me doy cuenta de que mi teléfono sigue en modo avión...

Para estados como éste, en los que la entrega del SMS también marca la finalización satisfactoria de la solicitud de envío, es posible que los webhooks entrantes lleguen en cualquier orden, por lo que no hay que fiarse exactamente de cuál llega primero.

Dado que la entrega del segundo mensaje indica que el envío se ha realizado correctamente, también se actualiza el estado de "completado":

{
    "template": "failover",
    "status": "completed",
    "timestamp": "2018-10-19T11:33:27.450Z",
    "usage": {
        "price": "0.0353",
        "currency": "EUR"
    },
    "dispatch_uuid": "de8d9eaf-8d10-407f-840b-53473f26c173",
    "_links": {
        "messages": [
            {
                "message_uuid": "afb5f546-97e7-44ba-97cd-bb7706a93f4e",
                "href": "v0.1/messages/afb5f546-97e7-44ba-97cd-bb7706a93f4e",
                "channel": "sms",
                "status": "submitted"
            },
            {
                "message_uuid": "e083fc2e-dffc-42c9-a7b3-446ee5fe67ba",
                "href": "v0.1/messages/e083fc2e-dffc-42c9-a7b3-446ee5fe67ba",
                "channel": "sms",
                "usage": {
                    "price": "0.0333",
                    "currency": "EUR"
                },
                "status": "delivered"
            }
        ]
    }
}

Dado que puede llevar cierto tiempo entregar los mensajes, estas actualizaciones asíncronas son una forma clave de interactuar con la API. En este ejemplo se configura primero el endpoint /status y esto es algo que recomendaría a cualquier desarrollador que trabaje con estas API.

Mensajería multicanal y resistente

En este tutorial has trabajado con una aplicación que puede enviar un mensaje a un cliente utilizando una sencilla SMS API que también ofrece integración con otros tipos de mensajes utilizando el mismo código y llamadas a la API (sólo tienes que enviar diferentes from y to datos). También has visto cómo podemos añadir resiliencia detectando cuándo un mensaje no ha llegado a un cliente y utilizando un canal alternativo para contactar con él.

¿Y ahora qué?

Si tiene ganas de hacer más cosas con estas API, aquí tiene algunos lugares que quizá le interese visitar a continuación:

Compartir:

https://a.storyblok.com/f/270183/250x250/e3d3b71060/lornajane.png
Lorna MitchellAntiguos alumnos de Vonage

Lorna es ingeniera de software con un incurable hábito bloguero. Intenta domar las palabras y el código a partes iguales.