Aller au contenu

Tape un mot pour chercher dans les pages.

↑↓ Choisir↵ OuvrirÉchap Fermer

Guides

Utiliser l'en-tête Idempotency-Key pour qu'un envoi rejoué après une coupure ne parte pas deux fois, et savoir quand réessayer ou non.

Une requête peut échouer après que l’e-mail est parti : coupure réseau, serveur qui répond trop tard, redémarrage de ton application. Ton code ne sait alors pas si le message est parti. S’il le renvoie, le destinataire reçoit deux fois la même facture.

L’en-tête Idempotency-Key règle ce problème. Tu donnes une clé à ton envoi. Si la même clé revient, l’API ne lance pas un second envoi : elle répond avec l’id du premier.

Ce qui est garanti

  • Une requête avec une clé déjà utilisée dans ton espace renvoie le même id. Rien ne repart.
  • La vérification de la clé se fait avant le contrôle de débit et celui du quota. Un rejeu ne peut donc pas être refusé parce que le quota est atteint.
  • Les clés sont propres à ton espace : deux espaces peuvent utiliser la même clé sans se gêner.

Comment l’envoyer

curl -X POST https://api.mail.hgdc.ch/emails \
-H "Authorization: Bearer $AGENTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: facture-118" \
-d '{
"from": "Factures <factures@envoi.atelier.ch>",
"to": "client@exemple.ch",
"subject": "Ta facture 118",
"html": "<p>Merci pour ta commande.</p>"
}'

L’API ne garde que les 256 premiers caractères de la clé, et 200 pour un lot. Reste en dessous, pour que deux clés différentes ne se confondent pas.

Choisir une clé métier

Ta clé doit venir de l’événement que tu envoies, pas d’un hasard généré à chaque tentative. Un UUID tiré au moment de l’envoi ne protège de rien : la nouvelle tentative aura une autre clé.

EnvoiClé conseillée
Facturefacture-118, avec le numéro de facture
Confirmation de commandecommande-2026-0457
Code de connexionconnexion-<id de l'utilisateur>-<id de la demande>
Accusé de réception d’un formulaireformulaire-<id de la soumission>

Une clé stable donne une garantie stable : envoyer la facture 118 deux fois, depuis deux serveurs différents, ne produit qu’un seul message.

En lot

Sur POST /emails/batch, la clé est découpée par position : la première entrée reçoit <clé>:0, la deuxième <clé>:1, et ainsi de suite.

curl -X POST https://api.mail.hgdc.ch/emails/batch \
-H "Authorization: Bearer $AGENTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: factures-octobre" \
-d '[
{ "from": "Factures <factures@envoi.atelier.ch>", "to": "client@exemple.ch", "subject": "Ta facture 118", "html": "<p>Merci.</p>" },
{ "from": "Factures <factures@envoi.atelier.ch>", "to": "lea@exemple.ch", "subject": "Ta facture 119", "html": "<p>Merci.</p>" }
]'

Un lot est tout ou rien : si une entrée est refusée (adresse invalide, quota atteint, domaine non vérifié), aucun e-mail du lot n’est créé. Tu peux donc le rejouer tel quel. Avec la même clé, un lot déjà accepté renvoie les mêmes id sans second envoi.

Quand réessayer

RéponseRéessayer ?Comment
Pas de réponse (coupure, délai dépassé)OuiAvec la même clé.
500 internal_server_errorOuiAvec la même clé, après une attente.
429 rate_limit_exceededOuiAvec la même clé, après quelques secondes. Trop d’envois en peu de temps.
429 daily_quota_exceededNon, pas aujourd’huiLe quota de la journée est épuisé. Attends le lendemain.
422 validation_errorNonCorrige la requête. Le même corps échouera encore.
401 ou 403NonClé absente, fausse ou révoquée.

Sans clé, ne réessaie jamais un envoi après une erreur réseau ou un 500. Tu ne sais pas si le premier est parti.

Une boucle de réessai en Node

Le client Mail réessaie déjà, deux fois par défaut, quand la requête s’y prête. Pour contrôler la politique toi-même, règle maxRetries à 0 et gère la boucle dans ton code. Le fichier du client est celui de Premier envoi depuis Node.js.

import { Agentmail, AgentmailError } from "./lib/agentmail"
const mail = new Agentmail({ maxRetries: 0 })
export async function envoyerFacture(numero: string, to: string, html: string) {
for (let essai = 1; essai <= 5; essai++) {
try {
const { id } = await mail.emails.send(
{ from: "Factures <factures@envoi.atelier.ch>", to, subject: `Ta facture ${numero}`, html },
{ idempotencyKey: `facture-${numero}` },
)
return id
} catch (error) {
const reessayable = error instanceof AgentmailError && error.retryable
if (!reessayable || essai === 5) throw error
await new Promise((resolve) => setTimeout(resolve, 1000 * essai))
}
}
throw new Error("Boucle d'envoi épuisée")
}

Trois points à retenir :

  • la clé est construite à partir du numéro de facture, donc identique à chaque tentative ;
  • error.retryable vaut true pour un 429 hors quota du jour, un 500 et une erreur réseau, et false pour le reste ;
  • la boucle s’arrête après cinq essais. Au-delà, l’erreur remonte à ton code, qui décide quoi faire : alerte, file d’attente, reprise manuelle.

Si ça ne marche pas

  • Un même envoi part deux fois : la clé n’était pas identique d’une tentative à l’autre, ou elle n’était pas envoyée. Vérifie que l’en-tête arrive bien, et qu’il ne contient pas d’heure ni d’identifiant aléatoire.
  • Un second e-mail ne part pas alors que son contenu a changé : la clé est déjà utilisée. Donne au nouvel envoi une clé propre.

Et ensuite