Product

Guía práctica para usar los webhooks de Mailgun

Aprende a configurar los webhooks, manejar los datos de tus emails transaccionales y proteger la información de los webhooks en esta publicación de un cliente de Mailgun.
Imagen para Guía práctica para usar los webhooks de Mailgun

Esta publicación invitada es obra de Opeyemi Obembe, un cliente de Mailgun de Nigeria. Cuando vimos el tutorial de Opeyemi sobre cómo configurar los webhooks, supimos que teníamos que compartirlo contigo. Su metodología no solo es muy sólida, sino que además es el tipo de desarrollador al que nos encanta apoyar.

Opeyemi se registró en Mailgun hace cuatro años y, desde entonces, ha creado cosas realmente increíbles. Hace poco ha desarrollado Suet, un proyecto de código abierto que ofrece analíticas e informes detallados sobre los emails transaccionales enviados a través de Mailgun. Puedes usarlo junto con nuestras nuevas funciones de analíticas para obtener información adicional sobre la interacción de tus emails.

Los emails transaccionales son esenciales para la mayoría de las aplicaciones. Enviamos emails de bienvenida, emails de recuperación de contraseña, notificaciones y mucho más. Y cuando lo hacemos, usamos proveedores como Mailgun. Enviar los emails está muy bien, pero ¿qué pasa con la entrega y el rendimiento? ¿Recibió ese usuario el email para restablecer la contraseña? ¿Se ha abierto ese email de notificación de “tarjeta de crédito a punto de caducar”?

Aunque estos datos están disponibles en el panel de control de tu cuenta de Mailgun, otra forma de recibir actualizaciones sobre lo que ocurre con tus mensajes transaccionales en Mailgun es a través de webhooksTambién existe la API, pero a diferencia de la API, donde tú “solicitas” estas actualizaciones (Poll), con los webhooks las actualizaciones se te envían directamente (Push). Lo único que tienes que hacer es proporcionar la URL de un script que pueda manejar los datos del evento a través de POST.

Sobra decir que el modelo Push tiene ciertas ventajas sobre el modelo Poll.

  • No tienes que hacer peticiones repetidas a la API. Esto supone consumir menos recursos del servidor.
  • Las actualizaciones son más en tiempo real porque se envían tan pronto como están disponibles en el servidor.

Configurar webhooks

Hay dos maneras de configurar los webhooks en Mailgun. Puede hacerse a través del panel de control de Mailgun o API. La forma más sencilla de hacerlo es a través del panel de control. Una vez que hayas iniciado sesión en el panel de control, un enlace de Webhooks está disponible en la barra de navegación.

webhooks1

En la página de los webhooks se enumeran los diferentes tipos de eventos de los que puedes recibir datos. Si haces clic en el icono “+” situado junto a cada evento, podrás establecer la URL a la que se enviarán los datos del evento.

Manejo de los datos

Para manejar los datos de eventos enviados a la URL de nuestro webhook, primero tenemos que saber qué aspecto tendrán. Los parámetros enviados a través de POST están disponibles en la documentación de la API. Podemos ir un paso más allá y confirmarlo usando una URL de webhook de prueba que registre los datos de Mailgun. Podemos usar el Postbin de Mailgun o requestb.in. Estos servicios generarán un punto de conexión único que podremos usar en el panel de control de Mailgun para obtener datos de eventos de muestra. Te recomiendo usar requestbin porque ofrece más detalles, como los encabezados de las peticiones. Estos encabezados son importantes porque es fácil pasar por alto el hecho de que Mailgun envía algunos datos usando el tipo de contenido [application/x-www-form-urlencoded] y otros como [multipart/form-data]. Pasar por alto estos pequeños detalles lo cambia todo en la forma de obtener los datos de los eventos.

Vamos a crear un punto de conexión de prueba para ver el aspecto de los datos de los eventos en comparación con lo que figura en la documentación.

Screen-Shot-2019-04-12-at-3
  • Repite este proceso para todos los eventos que te interesen.
  • Actualiza la página de requestbin para ver los datos de los eventos enviados.
requestbin-webhooks-data

Si te fijas bien en los datos de requestbin, te darás cuenta de lo que te comentaba sobre algunos datos que se envían como multipart/form-data.

Ahora que sabemos cuáles son los parámetros de cada tipo de evento y en qué tipo de contenido pueden venir, resulta fácil escribir código para gestionar los datos enviados. A continuación te mostramos un código sencillo que mostrará los detalles de las quejas y los emails descartados. (Estoy usando multer para manejar multipart/form-data).

                                

                                    const express = require('express')  rn    , bodyParser = require('body-parser')rn    , multer = require('multer')rn    ;rnrnconst app = express();  rnapp.use(bodyParser.urlencoded({extended: false}));  rnapp.listen(process.env.PORT || 3000);rnrnapp.post('/webhook', multer().none(), function(req, res) {  rn  const email = req.body.recipient;rn  const event = req.body.event;rnrn  if (event == 'complained') {rn    console.log(<code data-enlighter-language="generic" class="EnlighterJSRAW">${email} complained about your mail</code>);rn  }rn  else if (event == 'dropped') {rn    console.log(<code data-enlighter-language="generic" class="EnlighterJSRAW">Mail to ${email} dropped. ${event.description}</code>);rn  }rn  else if (event == 'bounced') {rn    console.log(<code data-enlighter-language="generic" class="EnlighterJSRAW">Error ${event.code}: Mail to ${email} bounced. ${event.error}</code>);rn  }rnrn  res.end();rn});
                                
                            

Garantizar la seguridad

No hay nada que impida que alguien que conozca la URL de nuestro webhook cree datos de eventos falsos y los envíe a dicha URL. Por suerte, Mailgun firma cada petición enviada e incluye también los siguientes parámetros:

  • timestamp (número de segundos transcurridos desde el 1 de enero de 1970)
  • token (cadena de caracteres generada de forma aleatoria con una longitud de 50)
  • signature (cadena de caracteres hexadecimales generada mediante el algoritmo HMAC)

Para verificar el token, debes hacer lo siguiente:

  • Concatenar los valores de timestamp y de token.
  • Codificar la cadena de caracteres resultante con HMAC, utilizando la clave de firma HTTP de tu webhook como clave y Sha256 como algoritmo.

El resultado debe ser idéntico al valor de signature.

A continuación te mostramos cómo se ve esto en Node.js:

                                

                                    const value = event_data_timestamp+event_data_token;  rnconst hash = crypto.createHmac('sha256', apikey)  rn                   .update(value)rn                   .digest('hex');rnif (hash !== event_data_signature) {  rn  console.log('Invalid signature');rn  return;rn}
                                
                            

Si añadimos eso a nuestro ejemplo de código original, obtendremos algo así:

                                

                                    const express = require('express')  rn    , crypto = require('crypto')rn    , multer = require('multer')rn    , bodyParser = require('body-parser')rn    ;rnrnconst app = express();  rnapp.use(bodyParser.urlencoded({extended: false}));  rnapp.listen(process.env.PORT || 3000);rnrnapp.get('/webhook', multer().none(), function(req, res) {  rn  // Validate signaturern  const value = req.body.timestamp+req.body.token;rn  const hash = crypto.createHmac('sha256',rn              process.env.API_KEY)rn                   .update(value)rn                   .digest('hex');rn  if (hash !== req.body.signature) {rn    console.log('Invalid signature');rn    return res.end();rn  }rnrn  // Log status of eventrn  const email = req.body.recipient;rn  const event = req.body.event;rnrn  if (event == 'complained') {rn    console.log(<code data-enlighter-language="generic" class="EnlighterJSRAW">${email} complained about your mail</code>);rn  }rn  else if (event == 'dropped') {rn    console.log(<code data-enlighter-language="generic" class="EnlighterJSRAW">Mail to ${email} dropped. ${event.description}</code>);rn  }rn  else if (event == 'bounced') {rn    console.log(<code data-enlighter-language="generic" class="EnlighterJSRAW">Error ${event.code}: Mail to ${email} bounced. ${event.error}</code>);rn  }rnrn  res.end();rn});
                                
                            

Podemos ir un paso más allá e incluir lo siguiente:

  • Comprobar cada petición frente a una memoria caché de tokens para evitar que se use el mismo token. Todos los tokens se almacenarán allí. Esto evitará los ataques de repetición (replay).
  • Comprobar que el valor de timestamp no esté demasiado alejado de la hora actual.

Mejorar la escalabilidad

Si envías muchos emails y esperas bastantes eventos, colocar el script del webhook en un servidor que no pueda escalar automáticamente es una mala idea. Incluso si no esperas muchos eventos, puede ocurrir cualquier imprevisto que provoque un aumento repentino. Tener un servidor que pueda escalar de forma automática resulta muy útil para este tipo de situaciones.

Aquí es donde entra en juego la computación sin servidor. En pocas palabras, la idea consiste en delegar la ejecución de tu código y todo lo relacionado con ella en un proveedor. Pueden ejecutarse múltiples instancias de tu código en paralelo y puedes ajustar sobre la marcha los recursos informáticos, como la memoria RAM y el tiempo de ejecución. Esto hace que sea altamente escalable. Además, se te cobrará en función de los recursos consumidos y el tiempo de ejecución, por lo que puede resultar muy barato si no envías gran cantidad de emails habitualmente.

Hay varios proveedores de computación sin servidor. Uno que yo utilizo y recomiendo es Google Cloud Functions por la facilidad para configurar las funciones HTTP. Una función HTTP es un bloque de código encapsulado como una función que puede activarse al visitar una URL. Eso es justo lo que necesitamos para nuestro webhook.

Para crear esta función, debemos escribir una función en JavaScript que se exportará como un módulo de Node.js. La función toma argumentos específicos de HTTP: request y response.

                                

                                    exports.webhook = function(request, response) {  rn  // Handle event data herern  response.send({status:"ok"});rn}
                                
                            

Dependiendo del tipo de contenido de la petición, el cuerpo de la misma se pasa de manera automática y queda disponible en el parámetro body del objeto de la petición.

                                

                                    exports.webhook = function(request, response) {  rn  let event = request.body.event; // deliveredrn  // Handle event data herern  // ...rn  response.send({status:"ok"});rn}
                                
                            

Sin embargo, esto no funciona para el tipo de contenido multipart/form-data. Y como ya sabemos, Mailgun envía algunos de los datos en formato multipart/form-data. Podemos incorporar una biblioteca como Multer utilizando require(). No obstante, debemos asegurarnos de que la dependencia conste en el archivo package.json.

                                

                                    const multer = require('multer');rnrnexports.webhook = function(request, response) {  rn  parser(request, response, function(){rn    console.log(request.body); // Our event datarn    // Handle event data herern    // ...rn    response.send({status:"ok"});rn  });rn}
                                
                            
                                

                                    {rn  "dependencies": {rn    "multer": "^1.3.0"rn  }rn}
                                
                            

A continuación, podemos publicar la función en Cloud Functions. Una forma sencilla de hacerlo es desde el panel de control de Cloud Functions.

  • Ve a la Google Cloud Console (si aún no tienes una cuenta, crea una).
  • Habilita Cloud Functions en el panel de control.
  • Haz clic en “Crear función”.
  • Escribe un nombre para la función (p. ej., “mailgun-webhook”).
  • En la sección del disparador, selecciona “Disparador HTTP”. Anota la URL, ya que esa será la URL de tu webhook.
  • Copia el código que maneja los datos de los eventos a la sección index.js de Cloud Functions.
  • Copia el contenido de tu package.json y pégalo en la sección package.json.
  • Selecciona o crea un segmento de etapa (Stage bucket). Ahí es sencillamente donde se aloja el código. Puedes usar cualquier cosa aquí.
  • En Función a ejecutar, introduce el nombre de la función (p. ej., “webhook”).
  • Guardar.

Ahora ya puedes usar la URL de la función en Mailgun como URL de tu webhook.

Conclusión

Trabajar con los webhooks de Mailgun es muy fácil. Hay muchas formas de usar los datos de eventos para enriquecer tus aplicaciones fuera de Mailgun. Por ejemplo, si permites que tu base de usuarios envíe emails desde el sitio web por el motivo que sea y utilizas Mailgun, puedes emplear los webhooks para ofrecerles analíticas. O a lo mejor quieres enviar las analíticas de los emails a otra plataforma. O tal vez prefieres que se te notifiquen los fallos de tu Slack cuenta. O puede que ni siquiera eso, quizá solo busques analíticas más detalladas. Sea cual sea el caso de uso, los datos de los eventos están a tu disposición.

Para ver un ejemplo del mundo real, echa un vistazo al código fuente del archivo del webhook de Suet.

¿Quieres probar los webhooks de Mailgun? ¡Regístrate!

Y si necesitas algo de ayuda para dar los primeros pasos con Mailgun, echa un vistazo a esta formación gratuita de Mailgun! Nuestro líder del equipo de Asistencia al cliente, Chris Hammer, te guiará durante todo el proceso de configuración y te ayudará a empezar a enviar, recibir y hacer el seguimiento de tus emails con Mailgun.