Dev Life
Optimiser l’envoi d’emails en masse par la gestion personnalisée des files d’attente avec l’API de Mailgun
L’envoi d’emails en masse demande beaucoup de tact et de précautions. Plusieurs facteurs viennent compliquer la tâche : la limitation et les quotas fixés par les fournisseurs de messagerie pour contrôler le flux d’emails, les lois sur la protection des données à respecter, ainsi que les problèmes de délivrabilité causés par des adresses invalides ou en double. De plus, vous devez gérer les seuils de connexion SMTP, les limites imposées par les fournisseurs et les retards imprévus.
Heureusement, il est possible de surmonter ces difficultés. Ce guide vous montre comment utiliser l’API de Mailgun comme baguette de chef d’orchestre pour optimiser l’envoi d’emails en masse tout en évitant les pièges courants. Mais d’abord, analysons pourquoi ces problèmes se posent et pourquoi ils peuvent faire dérailler vos campagnes d’emailing en masse.
Les défis de l’envoi d’emails en masse
Les quotas et la limitation mis en place par les services d’emailing (ESP) sont souvent les principaux responsables des problèmes de délivrabilité des emails en masse. Au-delà des seuils imposés par les fournisseurs, votre score de réputation d’expéditeur joue également un rôle essentiel. Lancer une campagne d’emailing en masse sans vérifier ce score ni comprendre les limites spécifiques de l’ESP peut rapidement dégrader votre réputation.
La baisse de votre score d’expéditeur crée un cercle vicieux : chaque rebond ou placement dans le dossier spam le diminue, ce qui nuit encore plus à votre délivrabilité. À mesure que ce score diminue, les ESP sont plus susceptibles de signaler ou d’ajouter votre domaine ou votre IP à une liste de blocage.
Même si cela pose moins de problème pour les systèmes de notification internes, les retards de délivrabilité (et la façon dont vous les gérez) peuvent perturber presque tous les cas d’usage de l’envoi d’emails en masse. La congestion du réseau, les filtres anti-spam agressifs et les problèmes de serveur peuvent ralentir la livraison, ce qui est particulièrement gênant pour les alertes urgentes. Ces retards peuvent également faire office de déclencheur pour l’envoi d’emails en double lorsque les systèmes renvoient automatiquement les messages non livrés, ce qui augmente le risque de placement dans le dossier spam et détériore encore votre réputation d’expéditeur.
Heureusement, vous pouvez résoudre bon nombre de ces problèmes en contrôlant plus finement comment et quand vos messages d’emails en masse sont envoyés. Plutôt que d’envoyer vos messages à l’aveugle, il vous faut une approche plus intelligente qui tient compte de plusieurs variables clés. C’est là qu’entrent en jeu les files d’attente.
En quoi les files d’attente sont utiles
Si votre réputation d’expéditeur vous inquiète, les files d’attente ne vont pas la rétablir directement ni immédiatement, mais elles peuvent contribuer à l’améliorer.
Les files d’attente sont des structures de données qui vous permettent de trier et catégoriser vos messages de façon programmatique, puis de les traiter successivement. Elles vous permettent ainsi de coordonner et contrôler les taux d’envoi de vos campagnes d’emailing en masse.
Par exemple, vous pouvez diviser et regrouper vos emails en masse par fournisseur de messagerie, créer une file d’attente distincte pour chaque groupe, puis définir des règles de traitement pour celles-ci. Cela s’avère particulièrement utile si vous devez planifier l’envoi d’emails aux heures creuses pour améliorer votre délivrabilité.
Les files d’attente facilitent également l’équilibrage de charge, car elles vous permettent de mettre en place de courts intervalles ou des pauses entre le traitement de chaque groupe d’emails. Vous pouvez aussi stocker temporairement les messages sortants, laissant ainsi au serveur le temps de traiter chaque lot.
Les systèmes de files d’attente rendent également la gestion des emails non livrés plus efficace. Vous pouvez configurer des stratégies de renvoi intelligentes pour relancer les messages ayant échoué à cause d’erreurs de réseau ou de serveur, tout en ignorant ceux dont les adresses sont invalides ou les boîtes de réception pleines. Vos files d’attente permettent également de créer une liste de rebonds pour filtrer les adresses de destinataires peu fiables.
Par ailleurs, les files d’attente contribuent à optimiser les campagnes d’emailing en masse en simplifiant le suivi des performances. Comme elles vous permettent de contrôler le flux de vos emails sortants, il est plus facile de repérer les blocages, d’affiner votre stratégie et de résoudre les problèmes.
Optimiser l’envoi d’emails en masse avec les files d’attente et Mailgun
Pour les besoins de ce guide, imaginons que vous créez un service de notification interne pour une grande multinationale dont le personnel et les filiales sont répartis dans différentes régions du monde. Certains messages seront destinés à toute l’entreprise, tandis que d’autres cibleront un lieu ou un département spécifique.
Choisir votre solution de file d’attente
La première chose à faire est de déterminer quels outils et composants vous seront utiles pour ce projet. Plusieurs options s’offrent à vous pour la gestion des files d’attente :
- RabbitMQ est un courtier de messages open source qui offre un routage flexible et prend en charge plusieurs protocoles. Grâce à une communauté très active, il propose des bibliothèques clientes et des outils de développement pour Java, .NET, Erlang, Python, PHP, JavaScript et Go.
- Amazon SQS est une excellente option si vous utilisez déjà l’écosystème AWS ou si vous souhaitez une solution entièrement gérée. Ses principaux atouts sont le support des systèmes de files d’attente distribués et son évolutivité.
- Apache Kafka est une plateforme open source de traitement et de streaming de données distribuées et polyvalente qui peut faire office de courtier de messages. Conçue pour traiter de gros volumes de données en temps réel, elle offre un débit supérieur à la plupart des systèmes de files d’attente ou courtiers de messages classiques. Cependant, la configuration et la gestion de Kafka peuvent s’avérer difficiles en raison de sa complexité.
- Redpanda est une alternative simplifiée et moins gourmande en ressources à Kafka. Compatible avec l’API de Kafka, elle offre un support de bon nombre d’implémentations similaires, notamment le courtage de messages.
Puisqu’il s’agit d’un projet d’envergure, une solution gérée avec mise à l’échelle automatique (comme SQS) est préférable. Cependant, si vous avez l’expérience et les ressources nécessaires pour vous en occuper, une solution open source peut réduire les coûts et offrir une meilleure personnalisation.
Toutefois, au lieu de gérer les emails vous-même, une approche plus astucieuse consiste à utiliser Sinch Mailgun. Non seulement ce service simplifie la livraison de vos emails, mais il vous permet de créer des files d’attente personnalisées. Vous gardez ainsi un contrôle total sans avoir à gérer l’infrastructure.
Aperçu de l’architecture
Les sections suivantes vous expliquent comment créer par programmation des files d’attente personnalisées pour gérer vos campagnes d’emailing en masse. Cet exemple utilise RabbitMQ pour la création et la gestion de files d’attente, ainsi que le SDK Java de Sinch Mailgun pour envoyer des messages d’email de façon asynchrone. Puisque RabbitMQ et Sinch Mailgun offrent le support d’une grande variété de langages et frameworks, vous pouvez adapter cet exemple au langage de programmation de votre choix.
En plus de l’ API HTTP et du SDK Java, Sinch Mailgun propose des kits de développement logiciel spécifiques pour Go, Node.js, PHP et Ruby.
Vous pouvez vous appuyer sur le schéma d’architecture suivant (et l’explication qui l’accompagne) pour mieux comprendre la structure et le fonctionnement du système de file d’attente et d’envoi d’emails en masse :

Ce système utilise une API Sinch Mailgun (MailgunMailingListApi) pour récupérer toutes les listes de contacts liées à votre compte. Vous pouvez créer et remplir des listes de diffusion par programmation à partir d’un client ou d’une application d’inscription d’utilisateur, ou bien manuellement depuis le tableau de bord de Sinch Mailgun. Les listes de diffusion vous permettent d’organiser et de gérer vos destinataires d’emails de manière structurée, et servent de source de données à votre système.
Une fois que le système a récupéré toutes les listes de diffusion, il crée une file d’attente RabbitMQ pour chacune d’elles. Le système remplit les files d’attente avec le contenu des emails ainsi que le nom et l’adresse de chaque destinataire (membre) qui recevra l’email.
Un module distinct, contenant des exécuteurs planifiés pour chaque file d’attente, guette l’arrivée de nouveaux éléments dans les files d’attente à des intervalles précis. Si l’un des exécuteurs détecte de nouveaux éléments dans la file d’attente, il commence son traitement. Lors de la dernière étape, le processeur d’email du système utilise MailgunMessagesApi pour envoyer le message.
Vous pouvez accroître la fiabilité du système en ajoutant une file d’attente ou une base de données pour gérer les messages qui n’ont pas pu être envoyés et archiver ceux qui ont réussi.
Créer vos files d’attente
Pour créer une file d’attente, vous devez configurer et exécuter une instance de RabbitMQ à l’aide de Docker. Une fois cela fait, vous pouvez créer un nouveau projet Java pour la création de la file d’attente. Assurez-vous d’ajouter les bibliothèques suivantes à la liste des dépendances de votre projet :
Il vous faudra aussi importer les éléments suivants dans votre classe principale :
// 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
Votre méthode main contiendra du code pour initialiser l’API de liste de diffusion de Sinch Mailgun, ouvrir une connexion à RabbitMQ et créer une file d’attente pour chaque liste de diffusion :
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}
Une fois les connexions nécessaires établies, le code génère une file d’attente pour chaque liste de diffusion en reprenant son nom. Une boucle for-each récupère chaque membre (MailingListMember) de la liste de diffusion correspondante. Ensuite, le code utilise les coordonnées de chaque membre (adresse et nom) ainsi que celles du message de l’email (objet et contenu des messages) pour créer et publier une entrée dans la file d’attente.
À ce stade, vous pouvez ajouter du code afin de créer d’autres files d’attente pour les types de messages/sujets. Par exemple, vous pourriez prévoir des files d’attente distinctes pour les newsletters, les notifications, les alertes et d’autres mises à jour importantes. Cela vous donnerait un contrôle plus fin sur la façon dont vos messages sont priorisés et envoyés. Toutefois, par souci de simplicité, vous pouvez ignorer cette étape.
Une fois le code exécuté avec succès, vous devriez pouvoir visualiser et gérer vos files d’attente depuis le tableau de bord de RabbitMQ :

Maintenant, il vous faut un client pour traiter les files d’attente et envoyer vos messages.
Traiter vos files d’attente et envoyer vos messages
Tout comme le module précédent, celui-ci nécessite d’ajouter le client amqp RabbitMQ, SLF4J et les bibliothèques de Mailgun comme dépendances. Vous devrez également ajouter la dernière bibliothèque Feign Core et importer les éléments suivants dans la classe principale de votre nouveau module :
// 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
Ensuite, vous devez réfléchir à la façon dont vous souhaitez traiter vos files d’attente et établir les règles appropriées. Par exemple, si vous voulez que chaque file d’attente soit traitée à des intervalles horaires différents, vous devrez configurer et créer 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 }
Pensez à ajouter le code ci-dessus à la classe main de votre nouveau module.
Comme l’application d’exemple possède trois files d’attente, il vous faut instancier un objet ScheduledExecutorService dont le pool de threads principal est de trois. Vous pouvez ensuite créer une tâche et définir un délai pour chaque file d’attente.
Dans cet exemple, chaque tâche exécutable porte le nom d’une file d’attente (liste de diffusion), mais vous êtes libre de nommer vos tâches comme bon vous semble. La méthode stopProcessing arrête le planificateur lorsque vous quittez l’application.
Vous pouvez à présent créer une méthode pour gérer vos files d’attente :
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}
La méthode consumeEmails établit une connexion à votre file d’attente, l’analyse, récupère les détails de chaque entrée, puis les transmet à sendEmail. Une fois l’email envoyé avec succès, il reçoit le libellé de traitement terminé dans le registre de la file d’attente.
Regardons de plus près la méthode 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}
Lorsque vous utilisez la méthode d’envoi standard (mailgunMessagesApi.sendMessage("", message)), Mailgun place votre message dans une file d’attente pour qu’il soit traité. Vous pouvez contourner cette file d’attente en envoyant le message de manière asynchrone. Pour ce faire, vous devez créer un AsyncClient et le lier à l’API Messages de Mailgun.
Une fois cela fait, le code construit votre message avec les informations de la file d’attente RabbitMQ, puis l’envoie à l’aide de l’API Messages de Mailgun.
Pour exécuter votre application, il vous faut ajouter un appel à la méthode startProcessing() dans votre méthode main et un peu de code pour gérer l’arrêt de l’application :
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 }
Conclusion
Dans cet article, vous avez découvert comment les files d’attente peuvent vous aider à gérer le flux de vos campagnes d’emailing en masse et vous offrir un contrôle minutieux sur la façon dont vos emails en masse sont envoyés. Associées à un système de gestion efficace, les files d’attente vous aident à atténuer les problèmes tels que les quotas, la limitation et la congestion du réseau.
Quelle que soit la manière dont vous mettez en œuvre et gérez vos files d’attente, un partenaire d’envoi fiable tel que Mailgun peut vous y aider.
En tant que plateforme API-first, Mailgun vous fournit tout un éventail d’outils pour envoyer, mais aussi faire évoluer de façon programmatique, surveiller et suivre vos campagnes. Consultez la documentation de la Send API officielle, et n’hésitez pas à vous inscrire à notre newsletter pour recevoir d’autres tutoriels comme celui-ci.