Symptôme : un événement figure dans l’Outbox, mais l’e-mail n’est pas visible dans la boîte de réception.
Solution rapide : vérifiez séparément la transaction métier, le traitement asynchrone, la réponse du service d’envoi et la réception réelle ; aucune de ces preuves ne remplace les autres.

Cette méthode s’adresse aux développeurs qui expédient des e-mails d’inscription, de validation ou de notification, aux équipes QA qui testent ces parcours et aux petites équipes qui maintiennent leurs tâches asynchrones.
Elle convient aux tests de votre propre produit ou à un environnement pour lequel vous avez reçu une autorisation explicite.

Distinguer les preuves avant de chercher la panne

Le test des e-mails Transactional Outbox ne se résume pas à vérifier qu’une ligne a été créée dans une table. Cette ligne peut prouver que l’application a enregistré un événement à traiter ; elle ne prouve pas, à elle seule, que le traitement a commencé, que le message a été remis à un serveur de messagerie ou que le destinataire peut le lire.

Le modèle Transactional Outbox vise à enregistrer l’événement à transmettre dans le même périmètre transactionnel que la modification métier correspondante. Ainsi, une validation de compte et l’événement « envoyer le message de validation » peuvent être validés ou annulés ensemble. Le guide de référence sur le modèle Outbox souligne également le risque de doublons lorsque les messages sont traités de nouveau : le consommateur doit donc tolérer une répétition plutôt que supposer qu’un événement ne sera traité qu’une fois.

Pour diagnostiquer un échec, identifiez d’abord le dernier état réellement observé :

  • La modification métier est-elle persistée ?
  • L’événement correspondant est-il persisté dans l’Outbox ?
  • Un worker ou un consommateur a-t-il pris en charge cet événement ?
  • Le service d’envoi a-t-il accepté le message ou signalé un échec ?
  • Le message est-il arrivé dans la boîte de test et son contenu est-il lisible ?

Ces questions séparent les étapes qui sont souvent confondues dans les journaux d’application. Un statut « terminé » peut décrire le traitement local d’une tâche sans indiquer le résultat chez le destinataire. De même, un appel d’envoi réussi ne vérifie ni le dossier de réception, ni les filtres appliqués par le fournisseur, ni le contenu visible.

À retenir : chaque assertion doit nommer la couche qu’elle vérifie. « L’événement existe », « le service d’envoi l’a accepté » et « le message est lisible dans la boîte de test » sont trois résultats différents.

Vérifier la transaction quand l’événement manque

Lorsqu’une opération métier aboutit mais que l’événement Outbox est absent, commencez par examiner le code qui écrit ces deux éléments. Le cas recherché est celui où les données métier et l’événement sont inscrits dans la même transaction de base de données, et non celui où une écriture séparée tente de corriger la situation après coup.

Reproduisez deux chemins avec votre propre produit :

  • Chemin validé : exécutez l’action métier, laissez la transaction se terminer normalement, puis vérifiez dans la base de test que la modification et l’événement sont tous deux persistés.
  • Chemin annulé : déclenchez une erreur après l’écriture métier, mais avant la validation de la transaction, puis vérifiez que ni la modification ni l’événement ne sont conservés.

L’interface ou la réponse HTTP n’est pas une preuve suffisante. Une réponse positive peut avoir été construite avant la validation effective de la transaction, ou un test peut inspecter un objet conservé en mémoire plutôt que l’état après commit. Il faut donc vérifier l’état persistant après la fin du scénario, en tenant compte du comportement transactionnel du cadre de test et de la base utilisée.

Comment tester le rollback sans envoyer de message ? Faites échouer le chemin avant le commit, puis contrôlez séparément la base et l’absence de tâche exploitable par le worker. Si le worker peut s’exécuter en parallèle, isolez le scénario ou bloquez explicitement l’envoi dans ce test : sinon, une tâche déclenchée trop tôt peut brouiller le résultat.

La même précaution s’applique aux tests qui englobent toute la méthode dans une transaction annulée à la fin. Selon le cadre utilisé, cette transaction de test peut empêcher un callback de commit de se déclencher, ou modifier le moment où les tâches asynchrones sont exécutées. La documentation officielle consacrée aux outils de test transactionnel décrit des comportements propres à son cadre : elle est utile pour comprendre ce type de frontière, mais ne permet pas de déduire le fonctionnement de tous les environnements.

Pour les tests de confidentialité, des données synthétiques suffisent généralement : adresse de test, nom fictif et jeton non associé à une personne réelle. Un contrôle des champs visibles dans les messages peut être complété par un audit de l’exposition des adresses, notamment pour éviter que des données personnelles issues d’un compte réel se retrouvent dans les journaux ou les captures d’écran de préproduction.

Localiser une tâche qui ne traite pas l’événement

Si l’événement existe mais que rien n’apparaît dans la boîte de test, observez son trajet entre la lecture de l’Outbox et l’exécution du consommateur. Cherchez des preuves distinctes : événement disponible, tâche créée ou publiée, worker actif, début de traitement, résultat du traitement et mise à jour du statut. Une entrée dans une file ou une table de tâches confirme seulement qu’un travail a été planifié ou enregistré.

Une méthode simple consiste à suivre un identifiant de corrélation stable, créé pour le scénario de test et présent dans les journaux pertinents. Il doit permettre de rapprocher l’événement Outbox, la tâche et la tentative d’envoi sans inclure l’adresse complète ou le contenu sensible du message dans les journaux. Vérifiez ensuite :

  • si le consommateur lit bien le type d’événement attendu ;
  • si un filtre de statut ou de date écarte l’événement ;
  • si le worker écoute la bonne file et le bon environnement ;
  • si la tâche échoue avant l’appel au service de messagerie ;
  • si une erreur est enregistrée sans que le statut Outbox soit actualisé.

Que faire si l’Outbox contient un événement, mais que le message n’arrive pas ? Comparez d’abord les traces de lecture et de traitement. Si aucune trace de prise en charge n’existe, le problème se situe avant l’envoi. Si le traitement est arrivé jusqu’au fournisseur, passez à l’analyse de sa réponse et de la boîte de réception : ne relancez pas aveuglément l’événement avant d’avoir déterminé si une première tentative a déjà été acceptée.

Comparer les preuves disponibles à chaque étape

Le tableau ci-dessous sert à choisir l’assertion adaptée au niveau de test. Les appréciations indiquent la portée habituelle de chaque contrôle, pas une garantie de fonctionnement dans tous les systèmes.

Contrôle Ce qu’il peut établir Ce qu’il ne prouve pas Portée
Assertion unitaire sur la création de l’événement Le code construit l’événement attendu Sa persistance ou son traitement réel Forte pour la logique locale, faible pour le parcours complet
Test transactionnel avec lecture de la base La modification métier et l’événement sont persistés ou annulés ensemble dans ce scénario Le réveil d’un worker indépendant Forte pour la frontière transactionnelle
Test d’intégration du consommateur Le consommateur traite l’événement et met à jour son état selon le scénario La réception effective par un destinataire externe Forte pour le traitement, moyenne pour l’envoi
Vérification d’une réponse SMTP ou d’une API de messagerie Le système distant a répondu à l’étape de remise considérée La présence dans la boîte de réception ou la lisibilité du message Forte pour l’échange, limitée pour la réception
Lecture dans une boîte de test contrôlée Le message est consultable à cette adresse pour ce scénario Un taux général de délivrabilité ou le comportement de toutes les boîtes Forte pour la réception observée

Cette comparaison évite de demander à un test unitaire de prouver un résultat de bout en bout. Pour les essais de réception, choisissez une adresse de test dont l’accès et le mode de consultation conviennent à votre environnement ; la matrice des usages d’adresses de test aide à réfléchir au choix selon le parcours testé. Une adresse temporaire peut être adaptée à une inscription ou à un message de validation ponctuel, tandis qu’un compte durable convient mieux à un flux qui exige des réponses ou un historique conservé.

Contrôler les reprises sans supposer une livraison unique

Un worker peut reprendre un événement si son traitement échoue, si le processus s’arrête au mauvais moment ou si l’accusé de traitement n’est pas enregistré. Il est donc possible qu’un message déjà accepté par le service d’envoi soit soumis à nouveau. Le guide officiel du modèle Outbox signale le risque de messages dupliqués : il ne faut pas promettre une livraison « exactement une fois » en se fondant uniquement sur un statut local.

Créez un scénario reproductible sur votre produit : faites traiter un même événement plus d’une fois, ou interrompez le consommateur entre l’appel d’envoi et la mise à jour de l’état. Vérifiez alors si le consommateur s’appuie sur une clé d’idempotence, un identifiant d’événement ou une autre règle métier pour éviter qu’une répétition ne déclenche un effet indésirable.

Comment repérer un doublon après une reprise ? Comparez l’identifiant de l’événement, les traces de chaque tentative, les changements de statut et les messages visibles dans la boîte de test. Le nombre de tentatives ou la durée de conservation d’une clé de déduplication dépend de la conception du système ; il n’existe pas ici de valeur universelle à appliquer sans examiner la politique de reprise et le comportement du fournisseur.

Contrôlez également les états incohérents : message accepté, événement encore marqué comme non traité ; tâche échouée, événement marqué comme envoyé ; ou événement terminé alors que la tâche n’a jamais atteint le service d’envoi. Le résultat attendu doit être défini selon les réponses réellement observées et la stratégie de reprise de l’application, pas selon un nom de statut isolé.

Interpréter une réponse d’envoi sans la confondre avec la réception

Un succès renvoyé par l’application indique souvent qu’une étape locale s’est déroulée sans erreur. Une réponse positive du service de messagerie constitue une preuve plus éloignée : le système distant a accepté ou traité une demande selon le protocole utilisé. Mais la réception lisible chez le destinataire reste une observation distincte.

Pour SMTP, le RFC 5321, qui définit le protocole et ses réponses, distingue l’acceptation d’un message par un serveur de son affichage effectif dans la boîte de réception finale. Une réponse positive de transfert ne permet donc pas d’affirmer que le message est visible, n’a pas été filtré ou peut être consulté par l’utilisateur. Le même raisonnement impose de ne pas assimiler la réponse d’une API à une preuve de lecture.

Une réponse positive du service d’envoi prouve-t-elle que le message a atteint la boîte de test ? Non. Elle confirme uniquement l’étape décrite par cette réponse. Pour attester la réception, ouvrez la boîte de test et vérifiez l’expéditeur, le destinataire, le sujet et le contenu attendu. Si le message manque, contrôlez l’adresse cible, l’environnement d’envoi, le routage et les éventuelles erreurs consignées par le service.

Ne confondez pas non plus une boîte vide avec une défaillance certaine de l’Outbox. Une adresse mal recopiée, un destinataire de substitution configuré en préproduction ou un filtrage côté réception peuvent expliquer l’absence du message sans que l’événement ou le worker soit en cause. Un repère sur les délais d’arrivée des e-mails peut aider à organiser l’observation ; il ne remplace pas la consultation effective du destinataire.

Précaution de test : utilisez une adresse contrôlée et des messages sans données personnelles réelles. Une réception vérifiée valide le scénario observé, pas la délivrabilité générale du système.

Adapter les tests à la transaction et aux tâches asynchrones

Quand un test passe, mais que la préproduction échoue, examinez les différences d’exécution plutôt que d’ajouter immédiatement des délais arbitraires. Un test peut annuler sa transaction, exécuter les tâches en mode synchrone ou remplacer le service d’envoi par une boîte de capture. Chacun de ces choix est utile, mais chacun retire une partie du trajet réel.

Distinguez donc les résultats dans le compte rendu :

  • Test unitaire : confirme la construction de l’événement et les règles locales.
  • Test transactionnel et d’intégration : vérifie l’écriture conjointe, le rollback et le traitement du consommateur dans le périmètre simulé.
  • Essai de réception : confirme que le message envoyé dans l’environnement choisi est consultable dans une boîte contrôlée.

Cette séparation rend les rapports plus utiles : un test réussi du générateur de messages ne doit pas être présenté comme une validation de livraison, et une réception ponctuelle ne valide pas nécessairement tous les chemins de reprise. Lorsque le cadre de test intercepte les messages, notez clairement que cette capture vérifie la composition ou l’appel d’envoi, non le passage par un serveur réel.

Utiliser la liste de contrôle avant de valider le parcours

Cochez chaque point dans l’environnement autorisé où le comportement doit être vérifié :

  • [ ] Le scénario de succès persiste la modification métier et l’événement Outbox dans la même frontière transactionnelle.
  • [ ] Le scénario de rollback ne laisse ni modification validée ni événement exploitable.
  • [ ] Les journaux permettent de relier l’événement à la tâche sans exposer inutilement les données personnelles.
  • [ ] Le worker ou le consommateur fournit une preuve distincte de prise en charge et de résultat.
  • [ ] Une reprise du même événement ne produit pas d’effet métier indésirable.
  • [ ] Les états de l’événement restent cohérents avec les résultats réellement observés.
  • [ ] La réponse du service d’envoi est rapportée comme une réponse d’envoi, et non comme une preuve de réception.
  • [ ] Un message de test est lisible dans l’adresse contrôlée et son contenu correspond au scénario attendu.
  • [ ] Les tests d’intégration décrivent leurs limites si le commit, le worker ou le serveur de messagerie est simulé.

Si certains contrôles ne peuvent pas être réalisés dans l’environnement de développement, classez-les comme non vérifiés et prévoyez une validation distincte en préproduction. Cette distinction est préférable à un verdict global « e-mail testé », qui masquerait précisément l’étape encore inconnue.

Pour un parcours ponctuel d’inscription, de validation ou de notification, une boîte temporaire contrôlée peut compléter les tests de l’Outbox : 24Forward permet d’obtenir une adresse sans inscription, de la consulter immédiatement et d’éviter d’exposer une boîte principale ou d’y accumuler des messages de test. Cela ne remplace pas un compte durable lorsqu’il faut répondre, conserver un historique ou récupérer un compte important. Pour valider la réception réelle dans votre propre parcours, vous pouvez choisir une adresse de test adaptée auprès de 24Forward et garder l’assertion de réception séparée de l’état Outbox.

Choisissez votre mode de réception selon la durée de la relation

Créez une adresse temporaire si vous attendez uniquement ce code ; pour la récupération et les notifications continues, configurez un alias de transfert pour le compte.