Dev Life
Optimización del envío masivo de email mediante la gestión de colas personalizadas con la API de Mailgun
El envío masivo de email requiere bastante cuidado y precisión. Varios factores añaden complejidad, como el bloqueo y los límites de frecuencia establecidos por los proveedores de servicios de email para controlar el flujo de emails, las leyes de protección de datos que deben cumplirse y los problemas de entregabilidad causados por direcciones no válidas o duplicadas. Además de eso, tienes que gestionar los umbrales de conexión SMTP, los límites impuestos por los proveedores y los retrasos imprevistos.
Afortunadamente, es posible sortear estos desafíos. Esta guía te mostrará cómo utilizar la API de Mailgun como tu batuta de director de orquesta para optimizar el envío masivo de email y evitar los problemas habituales. Pero primero, veamos por qué surgen estos problemas y por qué pueden arruinar tus campañas de envío masivo de email.
Los desafíos del envío masivo de email
Los límites de frecuencia y el bloqueo establecidos por los proveedores de servicios de envío de emails (ESP, por sus siglas en inglés) suelen ser los principales culpables de los problemas de entregabilidad del envío masivo de email. Más allá de los umbrales impuestos por los proveedores, tu puntuación de reputación como remitente también desempeña un rol importante. Iniciar una campaña de email masivo sin verificar tu puntuación ni comprender los límites específicos de los ESP puede mermar rápidamente tu reputación.
Una caída en la puntuación del remitente crea un círculo vicioso negativo, donde cada rebote o llegada a la carpeta de spam reduce tu puntuación y perjudica aún más la entregabilidad. Y a medida que disminuye tu puntuación, es más probable que los ESP marquen o añadan a su lista de bloqueo tus dominios o tu IP.
Aunque puede que esto no sea un problema para los sistemas de notificaciones internos, los retrasos en la entregabilidad (y cómo los gestionas) pueden afectar a casi cualquier caso de uso de envío masivo de email. La congestión de la red, los filtros de spam agresivos y los problemas del servidor pueden ralentizar la entrega, lo que resulta especialmente problemático para las alertas urgentes. Estos retrasos también pueden provocar emails duplicados cuando los sistemas reenvían automáticamente los mensajes no entregados, lo que aumenta el riesgo de llegada a la carpeta de spam y daña aún más tu reputación como remitente.
Afortunadamente, puedes solucionar muchos de estos problemas si ejerces un mayor control sobre cómo y cuándo se envían los mensajes de tu envío masivo de email. En lugar de bombardear con tus mensajes de forma aleatoria, necesitas un enfoque más inteligente que tenga en cuenta variables clave. Ahí es donde entran las colas.
Cómo pueden ayudar las colas
Si te preocupa tu reputación como remitente, las colas no la solucionarán de forma directa o inmediata, pero pueden ayudar a mejorarla.
Las colas son estructuras de datos que te permiten clasificar y categorizar mediante programación tus mensajes para luego procesarlos sucesivamente. Esto te permite coordinar y controlar las frecuencias de tus campañas de envío masivo de email.
Por ejemplo, podrías dividir y agrupar tus envíos masivos de email por proveedor de servicios de email, establecer una cola separada para cada grupo y luego fijar reglas sobre cómo se procesan tus colas. Esto puede ser de utilidad cuando necesitas programar emails durante las horas de menor actividad para aumentar la entregabilidad.
Las colas también pueden ayudar con el equilibrio de carga, ya que te permiten implementar pausas o intervalos cortos entre el procesamiento de cada grupo de emails. También puedes almacenar temporalmente los mensajes salientes, lo que da tiempo al servidor para procesar cada carga.
Los sistemas de colas también hacen más eficiente el manejo de emails no entregados. Puedes configurar estrategias de reenvío inteligentes para volver a intentar mensajes que fallaron debido a errores de red o del servidor, al tiempo que omites aquellos con direcciones no válidas o bandejas de entrada llenas. También puedes usar tus colas para establecer una lista de rebotes que filtre las direcciones de destinatarios poco fiables.
Más allá de eso, las colas ayudan a optimizar las campañas de envío masivo de email porque simplifican la monitorización del rendimiento. Como te permiten controlar el flujo de tus emails salientes, hacen que sea más fácil detectar cuellos de botella, refinar tu estrategia y solucionar problemas.
Optimización del envío masivo de email con colas y Mailgun
Para esta guía, imaginemos que estás desarrollando un servicio de notificaciones para los empleados de una gran empresa a nivel mundial con personal y sucursales en distintas regiones de todo el mundo. Algunos mensajes serán para toda la empresa, mientras que otros serán específicos de un lugar o departamento.
Selecciona tu solución de colas
Lo primero que tienes que hacer es averiguar qué herramientas y componentes necesitarás para este proyecto. Tienes las siguientes opciones para la gestión de colas:
- RabbitMQ es un agente de mensajes de código abierto que cuenta con un enrutamiento flexible y múltiples protocolos. Gracias a una próspera comunidad, dispone de bibliotecas de cliente y herramientas de desarrollo para Java, .NET, Erlang, Python, PHP, JavaScript y Go.
- Amazon SQS es una gran opción si ya trabajas con el ecosistema de AWS o quieres una solución totalmente gestionada. Sus mayores atractivos son su asistencia para sistemas de colas distribuidos y su escalabilidad.
- Apache Kafka es una plataforma multipropósito de procesamiento y streaming de datos distribuidos de código abierto que se puede utilizar como agente de mensajes. Como se creó para manejar grandes volúmenes de datos en tiempo real, ofrece un rendimiento superior a la mayoría de los agentes de mensajes o de colas tradicionales. Sin embargo, configurar y gestionar Kafka puede resultar difícil debido a su complejidad.
- Redpanda es una alternativa a Kafka simplificada y que consume menos recursos. Es compatible con las API de Kafka y ofrece asistencia para muchas de sus implementaciones, como la intermediación de mensajes.
Como este podría ser un proyecto bastante grande, es preferible utilizar una solución gestionada con escalado automático, como SQS. Sin embargo, si tienes la experiencia y los recursos para realizar una autogestión, una solución de código abierto puede reducir costes y ofrecer más personalización.
Pero en lugar de ocuparte de la gestión del email por tu cuenta, una solución más inteligente es Sinch Mailgun. No solo simplifica la entrega de emails, sino que además te permite crear colas personalizadas, lo que te da un control total sin las complicaciones de gestionar la infraestructura.
Visión general de la arquitectura
En los siguientes apartados te mostraremos cómo crear mediante programación colas personalizadas para gestionar tus campañas de envío masivo de email. Este ejemplo utiliza RabbitMQ para la creación y gestión de colas, y el SDK de Java de Sinch Mailgun para enviar mensajes de email de forma asíncrona. Como RabbitMQ y Sinch Mailgun brindan asistencia para una amplia variedad de lenguajes de programación y frameworks, puedes adaptar este ejemplo al lenguaje de programación que prefieras.
Junto a la API HTTP y el SDK de Java, Sinch Mailgun ofrece kits de desarrollo de software específicos por lenguaje para Go, Node.js, PHP y Ruby.
Puedes utilizar el siguiente diagrama de arquitectura (y su explicación posterior) para comprender mejor la estructura y el funcionamiento del sistema de colas y envío masivo:

Este sistema utiliza una API de Sinch Mailgun (MailgunMailingListApi) para obtener todas las listas de correo vinculadas a tu cuenta. Las listas de distribución se pueden crear y rellenar mediante programación desde un cliente o aplicación de registro de usuarios o manualmente a través del panel de control de Sinch Mailgun. Las listas de distribución te permiten organizar y gestionar los destinatarios de tus emails de forma estructurada y funcionan como la fuente de datos de tu sistema.
Una vez que el sistema ha obtenido todas las listas de distribución, se crea una cola de RabbitMQ para cada una de ellas. Las colas se llenan con los contenidos de los emails, así como con el nombre y la dirección de cada destinatario (miembro) que va a recibir el email.
Un módulo independiente que contiene ejecutores programados para cada cola está a la escucha de nuevos elementos en las colas a intervalos específicos. Si alguno de los ejecutores descubre nuevos elementos en la cola, empezará a consumir y procesar la cola. En el último paso, el procesador de emails del sistema utiliza MailgunMessagesApi para enviar el mensaje.
Puedes aumentar la fiabilidad del sistema si añades una cola o base de datos que gestione los mensajes que hayan fallado en su envío y archive los que se han enviado con éxito.
Crea tus colas
Para crear una cola, tienes que configurar y ejecutar una instancia de RabbitMQ mediante Docker. Cuando hayas terminado, puedes crear un nuevo proyecto en Java para la creación de colas. Asegúrate de añadir las siguientes bibliotecas a la lista de dependencias de tu proyecto:
También tendrás que añadir las siguientes importaciones a tu clase principal:
// Mailgun importsrnimport com.mailgun.api.v3.MailgunMailingListApi;rnimport com.mailgun.client.MailgunClient;rnimport com.mailgun.model.mailing.lists.MailingListData;rnimport com.mailgun.model.mailing.lists.MailingListMember;rnrn// RabbitMQ importsrnimport com.rabbitmq.client.AMQP;rnimport com.rabbitmq.client.ConnectionFactory;rnimport com.rabbitmq.client.Connection;rnimport com.rabbitmq.client.Channel;rnrn// Java importsrnimport java.io.IOException;rnimport java.util.HashMap;rnimport java.util.Map;rnimport java.util.concurrent.TimeoutException;rn
Tu método main se llenará de código para inicializar la API de listas de distribución de Sinch Mailgun, establecer una conexión con RabbitMQ y crear una cola para cada lista de distribución:
public static void main(String[] args) throws IOException, TimeoutException{rn // Initialize Mailing List APIrn // Replace with your own Sinch Mailgun API keyrn MailgunMailingListApi mailgunMailingListApi = MailgunClient.config("<SINCH_API_KEY>")rn .createApi(MailgunMailingListApi.class);rnrn // Initialize and create connection to RabbitMQ rn ConnectionFactory factory = new ConnectionFactory();rn // Replace with your detailsrn factory.setHost("<HOSTNAME>"); rn factory.setUsername("<USERNAME>");rn factory.setPassword("<PASSWORD>");rn factory.setPort(<PORT>);rnrn // Create queue channel - throws IOException and TimeoutExceptionrn Connection connection = factory.newConnection();rn Channel channel = connection.createChannel(); rnrn // Loop through mailing listsrn for(MailingListData mailingList : mailgunMailingListApi.getMailingList().getItems()) {rn String queue_name = mailingList.getName();rnrn // Create a simple queue using the mailing list name rn channel.queueDeclare(queue_name, false, false, false, null);rnrn // Populate queue with mailing list member addresses and message contentrn for(MailingListMember mailingListMember : mailgunMailingListApi.getMailingListMembers(mailingList.getAddress()).getItems()) {rn // Create headers with email message detailsrn Map<String, Object> headers = new HashMap<String, Object>();rn headers.put("address", mailingListMember.getAddress());rn headers.put("name", mailingListMember.getName());rn headers.put("subject", "Alert"); rn // Add a row to the queue rn // Replace with actual email contentrn String emailMessage = "Email content"; rn channel.basicPublish("", queue_name,rn new AMQP.BasicProperties.Builder()rn .headers(headers)rn .build(),rn emailMessage.getBytes());rnrn // Add code to log each queued entryrn System.out.println("Sent: " + emailMessage);rn } rnrn }rnrn}
Una vez establecidas las conexiones necesarias, el código genera una cola para cada lista de distribución a partir de su nombre. Un bucle for-each obtiene cada uno de los miembros (MailingListMember) de la lista de distribución correspondiente. A continuación, el código utiliza los datos de cada miembro (dirección y nombre) junto con los datos del mensaje de email (asunto y contenido del mensaje) para crear y publicar una entrada en la cola.
Llegados a este punto, podrías añadir código para crear colas adicionales según los tipos de asunto o de mensaje. Por ejemplo, podrías crear colas separadas para newsletters, notificaciones, alertas y otras actualizaciones importantes. Así tendrías un control más preciso sobre cómo se priorizan y envían tus mensajes. Sin embargo, en aras de la simplicidad, puedes saltarte este paso.
Una vez que el código se ejecute (con éxito), deberías poder ver y gestionar tus colas desde el panel de control de RabbitMQ:

Ahora necesitas un cliente que consuma las colas y envíe tus mensajes.
Consume tus colas y envía tus mensajes
Al igual que el módulo anterior, este requiere que añadas como dependencias las bibliotecas amqp-client de RabbitMQ, SLF4J y Mailgun. También tendrás que añadir la última biblioteca Feign Core y las siguientes importaciones a la clase principal de tu nuevo módulo:
// RabbitMQ importsrnimport com.rabbitmq.client.Channel;rnimport com.rabbitmq.client.ConnectionFactory;rnimport com.rabbitmq.client.Connection;rnimport com.rabbitmq.client.DeliverCallback;rnrn// Mailgun importsrnimport com.mailgun.api.v3.MailgunMessagesApi;rnimport com.mailgun.client.MailgunClient;rnimport com.mailgun.model.message.Message;rnimport com.mailgun.model.message.MessageResponse;rnrn// Feign Core importsrnimport feign.AsyncClient;rnimport feign.Client;rnrn// Java importsrnimport java.util.concurrent.ScheduledExecutorService;rnimport java.util.concurrent.TimeUnit;rnimport java.io.IOException;rnimport java.nio.charset.StandardCharsets;rnimport java.util.Map;rnimport java.util.concurrent.CompletableFuture;rnimport java.util.concurrent.ExecutorService;rnimport java.util.concurrent.Executors;rnimport java.util.concurrent.TimeoutException;rn
Luego, debes considerar cómo quieres que se procesen tus colas y establecer las reglas necesarias. Por ejemplo, si quieres que cada cola se procese en intervalos horarios diferentes, tendrás que configurar y crear un ScheduledExecutorService:
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(3);rnrn public void startProcessing() {rn Runnable china_task = () -> {rn try {rn consumeEmails("China");rn } catch (Exception e) {rn e.printStackTrace();rn }rn };rnrn Runnable britain_task = () -> {rn try {rn consumeEmails("Britain");rn } catch (Exception e) {rn e.printStackTrace();rn }rn };rnrn Runnable america_task = () -> {rn try {rn consumeEmails("America");rn } catch (Exception e) {rn e.printStackTrace();rn }rn };rnrn // Schedule each task to run at different hourly intervalsrn scheduler.scheduleWithFixedDelay(china_task, 11, 11, TimeUnit.HOURS);rn scheduler.scheduleWithFixedDelay(britain_task, 13, 13, TimeUnit.HOURS); rn scheduler.scheduleWithFixedDelay(america_task, 17, 17, TimeUnit.HOURS); rn }rnrn public void stopProcessing() {rn scheduler.shutdown();rn }
Recuerda añadir el código anterior a la clase main de tu nuevo módulo.
Como la aplicación de ejemplo tiene tres colas, tendrás que crear una instancia de un objeto ScheduledExecutorService con un tamaño de core thread pool de tres. A continuación, puedes crear una tarea y establecer un intervalo de retraso para cada cola.
En este ejemplo, cada tarea ejecutable lleva el nombre de una cola (lista de distribución), pero puedes nombrar las tareas como desees. El método stopProcessing apaga el programador cuando sales de la aplicación.
Ahora puedes crear un método para manejar tus colas:
public void consumeEmails(String queueName) throws IOException, TimeoutException {rnrn ConnectionFactory factory = new ConnectionFactory();rn // Replace with your detailsrn factory.setHost("<HOST>"); rn factory.setUsername("<USERNAME>");rn factory.setPassword("<PASSWORD>");rn factory.setPort(<PORT>);rnrn Connection connection = factory.newConnection();rn Channel channel = connection.createChannel();rnrn channel.queueDeclare(queueName, false, false, false, null);rn // Send the messagern DeliverCallback deliverCallback = (consumerTag, delivery) -> {rn String emailMessage = new String(delivery.getBody(), StandardCharsets.UTF_8);rn Map <String, Object> headers = delivery.getProperties().getHeaders();rnrn String recipient = headers.get("address").toString();rn String subject = headers.get("subject").toString();rn String name = headers.get("name").toString();rn String body = "Dear " + name + ",
" + emailMessage;rnrn sendEmail(recipient, subject, body);rn };rn channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });rn}
El método consumeEmails establece una conexión con tu cola, la analiza, obtiene los detalles de cada entrada y se los pasa a sendEmail. Cuando el email se envía con éxito, recibe una marca como consumido en el registro de la cola.
Veamos más de cerca el método sendEmail:
public void sendEmail(String recipient, String subject, String body) {rn // Create async clientrn ExecutorService executor = Executors.newFixedThreadPool(1);rn AsyncClient.Default<Object> asyncClient = new AsyncClient.Default<>(rn new Client.Default(null, null), executor);rnrn MailgunMessagesApi mailgunMessagesApi = MailgunClient.config("<SINCH_MAILGUN_API_KEY>")rn .client(asyncClient)rn .createApi(MailgunMessagesApi.class);rnrn Message message = Message.builder()rn .from("<REPLY_INBOX>")rn .to(recipient)rn .subject(subject)rn .text(body)rn .build();rnrn CompletableFuture<MessageResponse> messageResponse = mailgunMessagesApi.sendMessageAsync("<SINCH_MAILGUN_DOMAIN>", message);rn // Log message responsern System.out.println(messageResponse);rnrn}
Cuando utilizas el método de envío estándar (mailgunMessagesApi.sendMessage("", message)), Mailgun coloca tu mensaje en una cola para su procesamiento. Puedes omitir la cola si envías el mensaje de forma asíncrona. Para ello, es necesario que crees un AsyncClient que se enlace a la API de mensajes de Mailgun.
Una vez completado este paso, el código construye tu mensaje con los detalles de la cola de RabbitMQ y luego lo envía mediante la API de mensajes de Mailgun.
Para ejecutar la aplicación, debes añadir una llamada al método startProcessing() en tu método main y algo de código para gestionar el cierre de la aplicación:
public static void main(String[] args) { rn // Your main class rn SinchRabbitMQConsumer processor = new SinchRabbitMQConsumer(); rn processor.startProcessing();rnrn // Add a shutdown hook to stop processing when the application is terminatedrn Runtime.getRuntime().addShutdownHook(new Thread(processor::stopProcessing));rn }
Conclusión
En este artículo, has aprendido cómo las colas pueden ayudar a gestionar el flujo de tus campañas de email masivo y ofrecerte un control minucioso sobre cómo se efectúa tu envío masivo de email. Si se implementan mediante un sistema de gestión de colas competente, las colas te ayudan a atenuar problemas como los límites de frecuencia, el bloqueo y la congestión de la red.
Independientemente de cómo implementes y gestiones tus colas, un socio de envío fiable como Mailgun puede ayudarte.
Al ser una plataforma centrada en la API, Mailgun te ofrece un sinfín de herramientas no solo para enviar, sino para escalar de forma programática, monitorizar y rastrear tus campañas. Echa un vistazo a la documentación oficial de la Send API y asegúrate de suscribirte a nuestra newsletter para recibir más tutoriales como este.