# Envoyer sans doublon

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.

> **Attention**
>
> La clé ne compare pas le contenu. Une même clé avec un autre message renvoie l’`id` du premier, et le nouveau message ne part pas. Utilise une clé différente pour chaque envoi distinct.

## Comment l’envoyer

```bash
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é.

| Envoi                               | Clé conseillée                                       |
| ----------------------------------- | ---------------------------------------------------- |
| Facture                             | `facture-118`, avec le numéro de facture             |
| Confirmation de commande            | `commande-2026-0457`                                 |
| Code de connexion                   | `connexion-<id de l'utilisateur>-<id de la demande>` |
| Accusé de réception d’un formulaire | `formulaire-<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.

```bash
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éponse                                 | Réessayer ?          | Comment                                                                   |
| --------------------------------------- | -------------------- | ------------------------------------------------------------------------- |
| Pas de réponse (coupure, délai dépassé) | Oui                  | Avec la même clé.                                                         |
| `500 internal_server_error`             | Oui                  | Avec la même clé, après une attente.                                      |
| `429 rate_limit_exceeded`               | Oui                  | Avec la même clé, après quelques secondes. Trop d’envois en peu de temps. |
| `429 daily_quota_exceeded`              | Non, pas aujourd’hui | Le quota de la journée est épuisé. Attends le lendemain.                  |
| `422 validation_error`                  | Non                  | Corrige la requête. Le même corps échouera encore.                        |
| `401` ou `403`                          | Non                  | Clé 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](/docs/demarrer/premier-envoi-node/).

```ts
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

- [Envoyer depuis ton logiciel](/docs/guides/envoyer-depuis-ton-logiciel/) : la première requête, en cURL, en Node ou avec le SDK.
- [Erreurs](/docs/reference/erreurs/) : le détail de chaque code.
- [Limites](/docs/reference/limites/) : les plafonds de débit et de quota.
