Guide pratique
Requêtes idempotentes
Utilisez l’en-tête Idempotency-Key pour relancer en toute sécurité les requêtes d’envoi et de transfert. Format de clé, fenêtre de 24 heures, réexécutions, réponses 409 et 503, et stratégies de clés.
Les réseaux tombent en panne. Quand une requête d’envoi expire (timeout), vous ne pouvez pas savoir si Emailit l’a reçue, et la renvoyer risque d’envoyer deux fois l’e-mail à votre client. Un en-tête Idempotency-Key rend la relance sûre : Emailit traite la première requête et renvoie la même réponse pour toute répétition avec la même clé.
Fonctionnement
Ajoutez un en-tête Idempotency-Key à POST /emails ou à POST /emails/{id}/forward.
- Première requête. Emailit réserve la clé pour votre espace de travail et traite la requête.
- Succès. Emailit conserve la réponse pendant 24 heures. Toute requête avec la même clé pendant cette fenêtre reçoit la réponse conservée avec
200, et aucun nouvel e-mail n’est créé. - Échec. Si la requête échoue, par exemple avec
400ou402, Emailit libère la clé. Corrigez le problème et relancez avec la même clé. - Chevauchement. Si une deuxième requête arrive alors que la première est encore en cours, elle reçoit
409et rien n’est envoyé. Relancez peu après avec la même clé.
Les clés sont propres à votre espace de travail : deux espaces de travail peuvent donc utiliser la même clé sans conflit.
Format de la clé
| Règle | Valeur |
|---|---|
| Longueur | De 1 à 256 caractères |
| Caractères | Lettres A–Z et a–z, chiffres 0–9, trait d’union - et tiret bas _ |
| Portée | Par espace de travail |
| Fenêtre | 24 heures après la première réponse réussie |
Une clé contenant d’autres caractères, comme : ou /, est rejetée avec 400 Invalid Idempotency-Key.
Choisir une clé
Dérivez la clé de l’événement à l’origine de l’e-mail, pour que chaque chemin de relance produise la même clé :
| Exemple de clé | |
|---|---|
| Reçu de commande | order-1042-receipt |
| Réinitialisation du mot de passe | password-reset-7f3c9a1e (l’ID du jeton de réinitialisation) |
| Récapitulatif hebdomadaire | digest-user-881-2026-w40 |
| Tâche en arrière-plan | L’ID de la tâche, ou un UUID que vous générez quand vous mettez la tâche en file d’attente et que vous conservez avec elle |
Évitez les clés qui changent d’une tentative à l’autre, comme des horodatages ou un UUID généré dans la boucle de relance. Elles font passer chaque relance pour une nouvelle requête.
Envoyer avec une clé d’idempotence
Les exemples Node.js, Python et PHP relancent la requête en cas d’erreur réseau et de réponse 409, 429 ou 5xx, en réutilisant chaque fois la même clé. L’exemple cURL utilise le mécanisme de relance intégré de curl, qui couvre les timeouts, les 429 et la plupart des réponses 5xx.
curl https://api.emailit.com/v2/emails \
-H "Authorization: Bearer $EMAILIT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042-receipt" \
--retry 3 \
-d '{
"from": "Acme <orders@acme.com>",
"to": "ada@example.com",
"subject": "Receipt for order 1042",
"text": "Thanks for your order."
}'async function sendOnce(payload, key, attempts = 4) {
for (let attempt = 1; attempt <= attempts; attempt++) {
try {
const res = await fetch('https://api.emailit.com/v2/emails', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.EMAILIT_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': key,
},
body: JSON.stringify(payload),
});
if (res.ok) return res.json();
if (![409, 429].includes(res.status) && res.status < 500) {
throw new Error(`Send failed: ${res.status} ${await res.text()}`);
}
const wait = Number(res.headers.get('retry-after')) || attempt * 2;
await new Promise((r) => setTimeout(r, wait * 1000));
} catch (err) {
if (err.message.startsWith('Send failed') || attempt === attempts) throw err;
await new Promise((r) => setTimeout(r, attempt * 2000));
}
}
throw new Error('Send failed after retries');
}
const email = await sendOnce(
{
from: 'Acme <orders@acme.com>',
to: 'ada@example.com',
subject: 'Receipt for order 1042',
text: 'Thanks for your order.',
},
'order-1042-receipt',
);import os
import time
import requests
def send_once(payload, key, attempts=4):
for attempt in range(1, attempts + 1):
try:
res = requests.post(
"https://api.emailit.com/v2/emails",
headers={
"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}",
"Idempotency-Key": key,
},
json=payload,
timeout=30,
)
except requests.RequestException:
if attempt == attempts:
raise
time.sleep(attempt * 2)
continue
if res.ok:
return res.json()
if res.status_code not in (409, 429) and res.status_code < 500:
res.raise_for_status()
time.sleep(int(res.headers.get("retry-after", attempt * 2)))
raise RuntimeError("Send failed after retries")
email = send_once(
{
"from": "Acme <orders@acme.com>",
"to": "ada@example.com",
"subject": "Receipt for order 1042",
"text": "Thanks for your order.",
},
"order-1042-receipt",
)function sendOnce(array $payload, string $key, int $attempts = 4): array
{
for ($attempt = 1; $attempt <= $attempts; $attempt++) {
$ch = curl_init('https://api.emailit.com/v2/emails');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('EMAILIT_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . $key,
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($body !== false && $status >= 200 && $status < 300) {
return json_decode($body, true);
}
if ($body !== false && !in_array($status, [409, 429]) && $status < 500) {
throw new RuntimeException("Send failed: $status $body");
}
sleep($attempt * 2);
}
throw new RuntimeException('Send failed after retries');
}
$email = sendOnce([
'from' => 'Acme <orders@acme.com>',
'to' => 'ada@example.com',
'subject' => 'Receipt for order 1042',
'text' => 'Thanks for your order.',
], 'order-1042-receipt');Réponses
| Statut | Quand | Que faire |
|---|---|---|
200 |
Première requête réussie, ou sa réexécution dans les 24 heures | Utilisez la réponse. Une réexécution a le même corps, avec le même id. |
400 Invalid Idempotency-Key |
La clé est vide, trop longue ou contient des caractères non valides | Corrigez la clé. |
409 Idempotency key in progress |
Une requête avec la même clé est encore en cours de traitement | Patientez un instant, puis relancez avec la même clé. |
503 Idempotency unavailable |
Emailit n’a pas pu joindre son stockage d’idempotence et a donc refusé la requête plutôt que de risquer un doublon | Relancez avec la même clé. |
| Toute autre erreur | La requête a échoué et la clé a été libérée | Corrigez la cause et relancez avec la même clé. |
Les limites de débit sont vérifiées avant la clé : une relance peut donc encore recevoir 429. Attendez le délai indiqué par l’en-tête retry-after et renvoyez la même clé.
Voir aussi
- Idempotence dans la référence de l’API
- Envoyer un e-mail
- Transférer un e-mail
- Limites de débit
- Pourquoi mes e-mails sont-ils envoyés deux fois ?