Une intégration d'API e-mail fiable tient surtout à ce qui se passe quand quelque chose tourne mal. Le chemin nominal, c'est une seule requête HTTP. Les cas difficiles sont un timeout où vous ignorez si le message a été accepté, un worker qui plante au milieu d'un lot, et une boucle de nouvelles tentatives qui transforme une brève panne en déluge. Ce guide nous donne un ensemble de patterns qui garantissent que chaque e-mail est envoyé exactement une fois, ou au pire une fois de manière prévisible, quel que soit l'endpoint du fournisseur que vous appelez.
Nous décrivons ces patterns de façon générique afin qu'ils fonctionnent avec n'importe quelle API e-mail HTTP, y compris MigoSMTP. Lorsqu'une fonctionnalité précise compte, comme un en-tête d'idempotence, un endpoint de lot ou une limite de débit, consultez votre compte MigoSMTP ou la documentation de l'API pour les détails exacts plutôt que de les supposer. Si vous hésitez encore entre les protocoles, lisez d'abord SMTP ou API : comment choisir.
Pourquoi les envois d'e-mails échouent autrement que les autres appels d'API
La plupart des appels d'API peuvent être répétés sans dommage. Pas l'e-mail. Un second appel pour créer un utilisateur échoue généralement avec un conflit, mais un second appel pour envoyer un reçu envoie sans broncher un second reçu. Le client voit deux messages, le support voit une réclamation, et une réinitialisation de mot de passe envoyée deux fois peut invalider le premier code que la personne est en train de lire.
Le problème central est le résultat inconnu. Votre client envoie la requête, le serveur l'accepte, et la réponse est perdue à cause d'une coupure réseau ou d'un timeout. De votre côté, cela ressemble exactement à une requête qui n'est jamais arrivée. Si vous réessayez, vous risquez un doublon. Si vous ne réessayez pas, vous risquez de perdre le message. Chaque pattern ci-dessous existe pour lever cette ambiguïté.
| Échec | Ce que vous savez | Risque en cas de traitement naïf |
|---|---|---|
| Connexion refusée ou échec DNS | La requête n'a jamais atteint l'API | Réessai sans danger ; le seul risque est une tempête de nouvelles tentatives |
| Timeout de lecture après l'envoi de la requête | Rien sur l'acceptation | Réessayer sans clé de déduplication peut provoquer un double envoi |
| 429 Too Many Requests | L'API a rejeté l'appel pour des raisons de rythme | Un réessai immédiat aggrave la pression |
| 5xx de l'API ou d'une passerelle | En général non accepté, mais sans garantie | Même ambiguïté qu'un timeout dans certains cas |
| Erreur de validation 400 ou 422 | La requête elle-même est incorrecte | Réessayer indéfiniment gaspille de la capacité et masque un bug |
| Plantage du worker après l'appel d'API | Accepté, mais vous ne l'avez jamais enregistré | La tâche est redistribuée et l'e-mail est renvoyé |
Clés d'idempotence et prévention des envois en double
Une opération idempotente produit le même résultat quel que soit le nombre de fois où vous l'exécutez. De nombreuses API HTTP vous permettent de transmettre une clé d'idempotence, généralement dans un en-tête, de sorte qu'une requête répétée avec la même clé renvoie le résultat d'origine au lieu de créer une nouvelle ressource. L'IETF travaille activement sur un draft pour un champ d'en-tête Idempotency-Key standard, ce qui montre à quel point cette idée est répandue. La prise en charge d'un tel en-tête par MigoSMTP, et la durée pendant laquelle une clé est mémorisée, doivent être confirmées dans la documentation de l'API MigoSMTP. Nous ne le présumons pas ici.
La bonne nouvelle, c'est que vous n'avez pas besoin d'attendre un support côté serveur. Vous pouvez construire la même garantie de votre côté avec une table de déduplication. L'idée clé est que votre application décide de l'identité d'un e-mail avant de parler au réseau.
Choisir une clé de déduplication déterministe
Une bonne clé est dérivée de l'événement métier, et non générée au moment de l'envoi. Utilisez quelque chose comme order-1042-shipped ou user-77-password-reset-2026-10-15T09:14. Un UUID aléatoire créé dans le worker ne sert à rien, car une tâche redistribuée en créerait un nouveau. Incluez le type de message pour qu'un reçu et un avis d'expédition de la même commande n'entrent pas en collision, et n'ajoutez une tranche de temps ou un compteur que lorsque des envois répétés sont légitimes, par exemple pour un digest quotidien.
L'imposer avec une contrainte d'unicité
Les vérifications applicatives du type « chercher, puis insérer » sont sujettes aux conditions de concurrence. Placez un index unique sur la clé et laissez la base de données trancher. Le premier à écrire gagne et crée l'enregistrement d'envoi. Toute tentative ultérieure obtient soit une erreur de contrainte, soit retrouve la ligne existante et lit son état.
CREATE TABLE email_sends (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
dedupe_key VARCHAR(190) NOT NULL,
to_address VARCHAR(320) NOT NULL,
template VARCHAR(80) NOT NULL,
payload_json JSON NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
attempts INT NOT NULL DEFAULT 0,
next_try_at DATETIME NULL,
message_id VARCHAR(255) NULL,
last_error TEXT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
UNIQUE KEY uq_dedupe (dedupe_key)
);
Les statuts que nous utilisons sont pending, sending, accepted, failed et unknown. Accepted signifie que l'API a renvoyé un succès et que vous avez stocké l'identifiant de message. Unknown signifie qu'un appel a été effectué et que son issue n'est pas certaine, ce que nous traitons dans la section sur les nouvelles tentatives. Si l'API prend bien en charge une clé d'idempotence, transmettez aussi la même clé de déduplication à cet endroit, afin que les deux couches soient d'accord. Considérez le support côté serveur comme un second filet de sécurité, et non comme un remplacement.
Le pattern outbox : n'envoyer que ce qui a été validé
Le bug classique ressemble à ceci : votre code enregistre une commande, puis appelle l'API e-mail, puis retourne. Si l'appel d'API échoue, la commande existe sans e-mail. Si vous appelez l'API d'abord et que la validation en base échoue, le client reçoit une confirmation pour une commande qui n'existe pas. Deux systèmes ne peuvent pas être mis à jour dans une seule transaction, alors nous changeons le problème.
Avec le pattern outbox, la demande d'e-mail n'est qu'une ligne de plus, écrite dans la même transaction que la modification métier. Soit les deux sont validées, soit aucune. Un processus distinct livre ensuite les lignes de l'outbox à l'API. La livraison devient une tâche d'arrière-plan réessayable, et la base de données est l'unique source de vérité sur ce qui doit être envoyé.
- Transaction : insérez la commande et la ligne email_sends ensemble, avec le statut pending.
- Validation : ce n'est qu'après le commit que quoi que ce soit devient visible pour le processus d'envoi.
- Réservation : un worker sélectionne les lignes pending échues et les marque sending, en utilisant un verrouillage de ligne pour que deux workers ne prennent jamais la même ligne.
- Appel de l'API : envoyez la requête, puis enregistrez le résultat et l'identifiant de message sur la ligne.
- Réconciliation : une tâche périodique recherche les lignes bloquées en sending ou unknown et les résout.
Vous pouvez alimenter le worker en interrogeant la table toutes les quelques secondes, ou en publiant dans une file après le commit. L'interrogation est plus simple et comporte moins de pièces mobiles. Une file aide lorsque le volume est élevé, mais gardez la table comme enregistrement de référence, car les files peuvent redistribuer ou perdre des messages, alors que la table vous permet de demander ce qu'il est advenu de tel e-mail.
Files d'attente dans votre application : workers, concurrence et ordre
N'appelez pas l'API e-mail dans la requête web sans raison solide. Une réponse lente d'un tiers devient alors un paiement lent, et un pic d'inscriptions devient un pic de connexions ouvertes. Répondez à l'utilisateur dès que la ligne d'outbox est validée, et laissez des workers d'arrière-plan assurer la livraison.
- Bornez la concurrence. Un nombre fixe de workers, chacun avec un petit pool de connexions HTTP, vous donne un plafond prévisible de requêtes par seconde. Un parallélisme non borné est la façon dont une application atteint une limite d'API par accident.
- Séparez les files par urgence. Les réinitialisations de mot de passe et les codes à usage unique ne doivent jamais attendre derrière une newsletter. Utilisez des files ou des priorités distinctes, comme expliqué dans la séparation des flux transactionnels et marketing.
- Rendez les tâches idempotentes. Partez du principe que chaque tâche peut être livrée deux fois. La première étape d'une tâche consiste à charger la ligne et à s'arrêter si le statut est déjà accepted.
- Ne comptez pas sur l'ordre. Les nouvelles tentatives et les workers parallèles réordonnent les messages. Si l'ordre importe, par exemple la vérification avant la bienvenue, modélisez-le comme une dépendance dans vos données, pas comme un ordre de file.
- Faites expirer les e-mails périmés. Un code de connexion livré après vingt minutes est pire que pas de code. Donnez aux lignes sensibles au temps une expiration et marquez-les expired au lieu de les envoyer.
Gardez des charges utiles légères. Stockez le nom du modèle et les variables, ou le contenu final rendu, mais pas de grosses pièces jointes dans les messages de file. Référencez les fichiers stockés par identifiant, conformément aux limites expliquées dans notre guide sur les limites de taille et MIME.
Timeouts et classification des nouvelles tentatives
Chaque appel HTTP a besoin de timeouts explicites. Un client sans timeout attendra bien plus longtemps que ce qu'un humain tolère, et occupera un emplacement de worker pendant tout ce temps. Définissez un timeout de connexion court, souvent d'une ou deux secondes, et un timeout total assez long pour une réponse normale mais assez court pour échouer vite. Choisissez les valeurs d'après votre propre latence mesurée et les indications de votre documentation de l'API MigoSMTP, et non d'après un chiffre lu dans un article de blog.
Quand un appel se termine, classez son issue dans l'une de trois catégories. Cette classification est le cœur de l'intégration.
| Issue | Exemples | Action |
|---|---|---|
| Acceptée | 2xx avec un identifiant de message | Stocker l'identifiant, marquer accepted, s'arrêter |
| Définitivement rejetée, ne pas réessayer | 400, 401, 403, 404, 422, autres 4xx qui décrivent votre requête | Marquer failed, enregistrer le corps de l'erreur, alerter si elle se répète ; corriger le code, les identifiants ou les données |
| À réessayer plus tard, même clé de déduplication | 429, 500, 502, 503, 504, erreurs de connexion, erreurs DNS | Appliquer un backoff et réessayer ; respecter Retry-After |
| Issue inconnue | Timeout de lecture, réinitialisation de la connexion après l'envoi | Réessayer uniquement avec la même clé de déduplication, ou réconcilier d'abord |
La sémantique HTTP de ces codes, y compris l'en-tête Retry-After, est définie dans la RFC 9110, et la RFC 6585 définit le 429. Notez la différence avec SMTP. En SMTP, les réponses 4xx sont temporaires et les 5xx permanentes, ce que nous traitons dans notre guide des codes de rebond SMTP. Dans une API HTTP, les rôles sont différents : un 5xx signifie que le service a un problème et un 4xx que votre requête en a un. Ne mélangez pas les deux modèles. Un HTTP 400 n'est jamais un échec temporaire d'e-mail.
Backoff avec jitter
Les délais entre tentatives doivent croître de façon exponentielle et inclure de l'aléatoire, pour que mille workers ayant échoué ensemble ne reviennent pas tous ensemble. Une approche courante consiste à doubler un délai de base à chaque tentative, à le plafonner à un maximum, puis à lui appliquer une fraction aléatoire. Si la réponse inclut Retry-After, utilisez-le comme minimum. Fixez un nombre maximal de tentatives ou un âge maximal, au-delà duquel la ligne passe en failed et déclenche une alerte. Notre guide sur les limites de débit, les nouvelles tentatives et le backoff approfondit cette stratégie.
import random, time
RETRY_STATUS = {429, 500, 502, 503, 504}
def next_delay(attempt, retry_after=None, base=2.0, cap=300.0):
delay = min(cap, base * (2 ** attempt))
delay = random.uniform(delay / 2, delay)
if retry_after is not None:
delay = max(delay, float(retry_after))
return delay
def deliver(row, client):
try:
resp = client.post("/send", json=row.payload,
headers={"X-Dedupe-Key": row.dedupe_key},
timeout=(2, 15))
except (ConnectionError, TimeoutError):
return ("retry", next_delay(row.attempts))
if 200 <= resp.status_code < 300:
return ("accepted", resp.json().get("id"))
if resp.status_code in RETRY_STATUS:
ra = resp.headers.get("Retry-After")
return ("retry", next_delay(row.attempts, ra))
return ("failed", resp.text[:500])
Le nom d'en-tête de cet exemple est inventé à titre d'illustration. Utilisez celui que précise la documentation de l'API MigoSMTP, ou appuyez-vous uniquement sur votre propre table si aucun en-tête de ce type n'existe.
Traitement des réponses et stockage des identifiants de message
Une réponse de succès n'est pas la fin de l'histoire, c'est le début du suivi. Stockez immédiatement l'identifiant de message de la réponse sur votre enregistrement d'envoi, dans la même étape que celle qui marque la ligne comme accepted. Les événements de webhook de livraison, de rebond et de plainte peuvent alors y être rattachés. La fiabilité de cette seconde moitié est traitée dans la gestion fiable des webhooks e-mail.
- Conservez le corps brut de la réponse de l'API pendant une courte période. Il est précieux pour le débogage.
- Enregistrez des horodatages pour la création, la première tentative, l'acceptation et le dernier événement. Les écarts entre eux montrent où surviennent les retards.
- Comprenez ce que signifie « accepté ». Une réponse 2xx d'une API e-mail signifie normalement que le service a mis le message en file pour livraison, et non que le serveur du destinataire l'a accepté. La livraison finale est signalée plus tard par des événements.
- N'analysez pas les messages d'erreur comme de la logique. Utilisez les codes de statut et les éventuels codes d'erreur documentés, et traitez le texte libre comme une information pour les humains.
- Indexez la colonne de l'identifiant de message pour que le support puisse passer d'un identifiant présent dans un log à une fiche client en une seule requête.
Masquez les secrets et les données personnelles dans les logs. Journalisez la clé de déduplication, l'identifiant d'envoi, le statut et l'identifiant de message, mais pas les clés d'API ni le corps complet des messages. Les clés doivent être stockées et renouvelées comme décrit dans la sécurité des clés d'API.
Traitement par lots et envois en masse
Envoyer à de nombreux destinataires est une charge de travail différente de l'envoi d'un seul reçu. Vous gérez désormais le débit, la mémoire et l'équité vis-à-vis du reste du trafic. Certaines API e-mail proposent un endpoint de lot qui accepte de nombreux messages en une seule requête, d'autres attendent un message par requête avec de nombreux appels concurrents. Consultez votre compte MigoSMTP ou la documentation de l'API pour savoir ce qui est disponible et connaître les limites de destinataires par requête avant de concevoir autour de l'une ou l'autre approche.
- Découpez l'audience. Lisez les destinataires par pages depuis la base de données, et non sous forme d'une liste géante en mémoire. Créez des lignes d'envoi par destinataire, puis laissez les workers les traiter.
- Personnalisez par destinataire. Évitez de mettre de nombreux destinataires en To ou en Cc. Chaque personne doit recevoir son propre message, avec son propre lien de désinscription et ses propres données de suivi.
- Rythmez l'envoi. Étalez les grandes campagnes sur une fenêtre de temps plutôt que de tout lâcher d'un coup. Le rythme protège aussi votre réputation, en particulier sur les nouveaux domaines, comme décrit dans le guide de warm-up.
- Rendez la campagne reprenable. Comme chaque destinataire a une ligne avec une clé de déduplication unique, par exemple campaign-318-user-9001, vous pouvez arrêter et relancer un envoi à tout moment sans double envoi.
- Gérez les échecs partiels. Si un appel de lot peut réussir pour certains messages et échouer pour d'autres, stockez les résultats message par message. Ne marquez jamais un lot entier comme accepté ou échoué sur la base d'un seul code de statut, sauf si la documentation indique que c'est ainsi que cela fonctionne.
Ne négligez pas les bases de la gestion des listes : l'envoi en masse par API ne change pas les règles de consentement, de suppression et de désinscription. Filtrez les adresses supprimées avant de créer les lignes d'envoi, en suivant l'approche décrite dans notre guide de la liste de suppression.
Exemple concret : une panne, deux conceptions
Voici un exemple inventé, avec des chiffres ronds. Une boutique en ligne envoie une confirmation pour chaque commande. Par un après-midi chargé, elle crée 6 000 commandes en une heure, et l'API e-mail connaît une période de 4 minutes de réponses lentes et d'erreurs 503 intermittentes.
| Étape | Conception naïve | Outbox avec clé de déduplication |
|---|---|---|
| Commande créée | La requête appelle directement l'API | La requête écrit la commande et la ligne d'envoi, et répond aussitôt |
| API lente pendant 4 minutes | Le paiement attend sur un long timeout, certains clients voient des erreurs et cliquent de nouveau sur Payer | Paiement non affecté ; les lignes attendent en pending |
| Timeout sur 150 appels | Le code réessaie immédiatement ; beaucoup de ces messages avaient déjà été acceptés | Lignes marquées unknown ; réessayées plus tard avec la même clé de déduplication |
| Résultat | 150 commandes avec environ 2 confirmations chacune, plus des commandes en double dues aux doubles clics | Environ 150 messages retardés de quelques minutes ; zéro doublon |
| Impact sur le support | Des dizaines de tickets demandant quel e-mail est le bon | Aucun ; l'alerte d'âge de file s'est déclenchée puis éteinte |
Avec 6 000 commandes dans l'heure, la moyenne est de 100 par minute, donc un ralentissement de 4 minutes laisse un arriéré d'environ 400 lignes. Si les workers peuvent envoyer 300 par minute une fois l'API rétablie, l'arriéré se résorbe en 2 minutes environ (400 lignes divisées par les 200 par minute de capacité excédentaire au-delà des 100 par minute de nouvelles arrivées). Ce calcul vaut la peine d'être fait pour votre propre système : la capacité excédentaire de vos workers détermine la vitesse de récupération, et le plafond de concurrence détermine si la récupération reste dans les limites de votre compte.
Prise en compte des limites de débit
Les limites de débit prennent des formes telles que des requêtes par seconde, des messages par jour, des connexions simultanées et des destinataires par requête. Celles qui s'appliquent à votre compte, et leurs valeurs exactes, figurent dans votre compte MigoSMTP ou la documentation de l'API. Ne les devinez pas. Construisez votre client de sorte que ces chiffres soient de la configuration, et non des constantes dans le code.
- Utilisez un token bucket ou un délai fixe entre les requêtes dans chaque worker, avec un plafond global sur l'ensemble des workers.
- Sur un 429, cessez d'envoyer depuis ce worker pendant la durée de Retry-After, ou pendant un délai de backoff si aucune n'est donnée. Ne réessayez pas simplement le même appel.
- Surveillez les succès lents. Une tendance à la hausse de la latence précède souvent les erreurs, et c'est un signal pour réduire la concurrence.
- Utilisez un circuit breaker. Après une série d'échecs, suspendez les appels pendant une courte fenêtre, puis testez avec une seule requête avant de reprendre à pleine vitesse.
- Gardez à l'esprit les limites côté réception. L'acceptation par le fournisseur n'est que le premier saut. Les fournisseurs de messagerie appliquent leurs propres limites, traitées dans notre guide des exigences pour les expéditeurs en masse.
Dégradation maîtrisée quand l'API est indisponible
Tout service externe sera parfois indisponible. Décidez à l'avance de ce que fait votre produit, pour que la réponse ne soit pas improvisée pendant un incident.
| Type d'e-mail | Comportement suggéré pendant une panne |
|---|---|
| Réinitialisation de mot de passe, code à usage unique | Mettre en file avec une expiration courte ; indiquer à l'écran que le message peut être retardé et proposer un renvoi après un délai d'attente |
| Confirmation de commande ou de paiement | Mettre en file et livrer en retard ; afficher aussi la confirmation sur la page de la commande |
| Notifications de compte | Mettre en file et livrer en retard ; envisager d'en regrouper plusieurs en un seul |
| Newsletters et campagnes | Mettre la campagne en pause et la reprendre plus tard ; ne jamais se précipiter pour rattraper le retard |
| Alertes internes | Basculer vers un autre canal, comme le chat ou un second fournisseur |
Configurez des alertes sur la profondeur de la file et sur l'âge de la plus ancienne ligne pending, et pas seulement sur le nombre d'erreurs. Une file qui grossit sans erreur signifie quand même que des clients attendent. Fixez un plafond à la file pour que la mémoire et le stockage ne puissent pas croître sans limite, et décidez ce qui est abandonné en premier si le plafond est atteint. Si vous prévoyez un second fournisseur en secours, souvenez-vous du travail que représentent le warm-up et l'authentification d'un second chemin, comme décrit dans notre guide de migration.
Tester l'intégration
Des tests qui vérifient seulement une réponse 200 ne vous protégeront pas. Le comportement important est ce qui se passe en cas d'échec, alors facilitez la production des échecs. Exécutez votre client contre un serveur factice ou un sandbox à qui l'on peut demander de répondre lentement, de renvoyer un 429 avec Retry-After, de renvoyer un 503, de couper la connexion après avoir lu la requête, ou de renvoyer un corps mal formé. Utilisez un mail catcher pour les vérifications de contenu, en suivant notre guide de l'environnement de staging.
- Test de tâche en double : mettez la même tâche deux fois en file et vérifiez qu'une seule requête atteint l'API factice.
- Test de plantage : tuez un worker après l'appel d'API et avant la mise à jour de la base, puis vérifiez que le réconciliateur résout la ligne sans second envoi.
- Test de timeout : faites en sorte que le serveur factice accepte la requête puis se fige, et vérifiez que la ligne finit accepted exactement une fois.
- Test de message empoisonné : envoyez une charge utile qui renvoie 422 et confirmez qu'elle passe en failed après une seule tentative, avec l'erreur stockée.
- Test de charge : lancez un pic réaliste contre le staging pour confirmer que les plafonds de concurrence et le backoff se comportent comme prévu.
Checklist avant la mise en production
- Chaque e-mail a une clé de déduplication déterministe et un index unique la fait respecter.
- Les lignes d'envoi sont écrites dans la même transaction que l'événement métier (outbox).
- La prise en charge d'un en-tête d'idempotence est confirmée dans la documentation de l'API MigoSMTP, et notre propre table est utilisée quoi qu'il en soit.
- Ce sont les workers, et non les requêtes web, qui appellent l'API, avec une concurrence bornée.
- Des timeouts de connexion et totaux sont définis sur chaque appel.
- Les réponses sont classées : accepted, retry, failed, unknown. Les autres réponses 4xx ne sont jamais réessayées à l'aveugle.
- Le backoff a du jitter, un plafond, un nombre maximal de tentatives et respecte Retry-After.
- Les identifiants de message sont stockés sur les lignes d'envoi et indexés.
- Les envois en masse sont découpés, rythmés et reprenables, avec un filtrage des suppressions avant la création des lignes.
- Les limites de débit sont de la configuration, issue de votre compte ou de la documentation de l'API.
- Des alertes existent pour l'âge de la plus ancienne ligne pending, le nombre d'échecs et le taux de 429.
- Les tests d'échec pour les doublons, les plantages, les timeouts et les 429 passent en staging.
Envie d'un second regard sur votre conception d'envoi ? Dites-nous comment votre application envoie des e-mails aujourd'hui et où les nouvelles tentatives ou les doublons vous inquiètent, et nous vous aiderons à planifier une intégration sûre.
Lectures complémentaires et sources
Questions fréquentes
Qu'est-ce qu'une clé d'idempotence pour une API e-mail ?
C'est une valeur unique que vous joignez à une requête afin que le serveur puisse reconnaître la répétition d'une même requête et éviter de faire le travail deux fois. Pour l'e-mail, cela signifie qu'une nouvelle tentative après un timeout ne produit pas un second exemplaire du message. La prise en charge d'un en-tête d'idempotence par MigoSMTP, ainsi que son fonctionnement, doivent être confirmés dans la documentation de l'API MigoSMTP. Dans tous les cas, vous pouvez obtenir la même protection avec votre propre table de déduplication.
Si l'API prend en charge l'idempotence, ai-je encore besoin de ma propre table de déduplication ?
Oui, nous le recommandons. L'idempotence côté serveur ne couvre généralement qu'une fenêtre limitée et uniquement les requêtes qui parviennent jusqu'à l'API. Votre propre table bloque aussi les tâches en double, les déclenchements de webhooks en double et les doubles clics en amont, et elle vous fournit une piste d'audit de ce que vous aviez l'intention d'envoyer.
Est-il sûr de réessayer après un timeout ?
Seulement si vous disposez d'une stratégie de déduplication. Un timeout signifie que vous ne savez pas si l'API a accepté le message. Sans clé d'idempotence ni recherche par votre propre référence, une nouvelle tentative peut envoyer l'e-mail deux fois. Avec l'une des deux, la nouvelle tentative est sûre.
Quels codes de statut HTTP dois-je réessayer ?
Réessayez les échecs réseau, les timeouts, les 429 Too Many Requests et les erreurs serveur 5xx, avec un backoff exponentiel assorti de jitter, en respectant tout en-tête Retry-After. Ne réessayez pas les autres erreurs 4xx telles que 400, 401, 403 ou 422, car la même requête échouera de la même façon tant que vous n'aurez rien modifié.
Qu'est-ce que le pattern outbox ?
Vous écrivez la demande d'e-mail dans une table, dans la même transaction de base de données que la modification métier, puis un processus distinct lit les lignes non envoyées et appelle l'API. Cela supprime l'écart où la validation en base réussit mais l'appel d'e-mail échoue, ou l'inverse.
Dois-je envoyer les e-mails depuis la requête web ou depuis une file d'attente ?
Depuis une file d'attente pour presque tous les cas. Une file protège la latence côté utilisateur, permet de réessayer en toute sécurité et vous laisse contrôler le débit. L'exception est un outil interne très simple où un envoi en échec peut simplement être affiché à la personne qui l'a déclenché.
Comment envoyer une grande campagne via une API ?
Répartissez les destinataires en lots, plafonnez la concurrence, respectez les limites documentées et Retry-After, et étalez l'envoi dans le temps. Consultez votre compte MigoSMTP ou la documentation de l'API pour connaître les limites exactes et savoir s'il existe un endpoint de lot. Pour les nouveaux domaines ou les nouvelles IP, suivez également un plan de warm-up.
Pourquoi stocker l'identifiant de message renvoyé par l'API ?
C'est la clé de jointure entre votre enregistrement d'envoi et les événements ultérieurs tels que les webhooks de livraison, de rebond et de plainte. Sans lui, les questions du support comme « ce client a-t-il reçu son reçu ? » relèvent de la devinette.
Comment tester une intégration e-mail sans envoyer de vrais messages ?
Exécutez le client d'API contre un serveur factice ou un sandbox capable de renvoyer à la demande des réponses lentes, des 429 et des 5xx, et utilisez un mail catcher pour vérifier le contenu. Faites vos assertions sur votre table d'envois, pas seulement sur les appels HTTP. Notre guide sur les environnements de staging décrit des configurations sûres.