Guida pratica
Richieste idempotenti
Usa l’header Idempotency-Key per ritentare in sicurezza le richieste di invio e di inoltro. Formato della chiave, finestra di 24 ore, risposte ripetute, risposte 409 e 503 e strategie per le chiavi.
Le reti si guastano. Quando una richiesta di invio va in timeout, non puoi sapere se Emailit l’ha ricevuta, e inviarla di nuovo potrebbe far arrivare due email al tuo cliente. Un header Idempotency-Key rende sicuro il nuovo tentativo: Emailit elabora la prima richiesta e restituisce la stessa risposta a qualsiasi ripetizione con la stessa chiave.
Come funziona
Aggiungi un header Idempotency-Key a POST /emails o POST /emails/{id}/forward.
- Prima richiesta. Emailit riserva la chiave per il tuo workspace ed elabora la richiesta.
- Successo. Emailit salva la risposta per 24 ore. Qualsiasi richiesta con la stessa chiave in quella finestra riceve la risposta salvata con
200, e non viene creata nessuna nuova email. - Errore. Se la richiesta non riesce, ad esempio con
400o402, Emailit libera la chiave. Risolvi il problema e ritenta con la stessa chiave. - Sovrapposizione. Se arriva una seconda richiesta mentre la prima è ancora in corso, riceve
409e non viene inviato nulla. Ritenta poco dopo con la stessa chiave.
Le chiavi valgono per il singolo workspace, quindi due workspace possono usare la stessa chiave senza conflitti.
Formato della chiave
| Regola | Valore |
|---|---|
| Lunghezza | Da 1 a 256 caratteri |
| Caratteri | Lettere A–Z e a–z, cifre 0–9, trattino - e trattino basso _ |
| Ambito | Per workspace |
| Finestra | 24 ore dalla prima risposta riuscita |
Una chiave con altri caratteri, come : o /, viene rifiutata con 400 Invalid Idempotency-Key.
Scegli una chiave
Ricava la chiave dall’evento che genera l’email, così ogni percorso di nuovo tentativo produce la stessa chiave:
| Chiave di esempio | |
|---|---|
| Ricevuta di un ordine | order-1042-receipt |
| Reimpostazione della password | password-reset-7f3c9a1e (l’ID del token di reimpostazione) |
| Riepilogo settimanale | digest-user-881-2026-w40 |
| Job in background | L’ID del job, oppure un UUID che generi quando metti in coda il job e salvi insieme a esso |
Evita le chiavi che cambiano tra un tentativo e l’altro, come i timestamp o un UUID generato dentro il ciclo dei nuovi tentativi. Fanno sembrare ogni nuovo tentativo una nuova richiesta.
Invia con una chiave di idempotenza
Gli esempi in Node.js, Python e PHP ritentano in caso di errori di rete e di risposte 409, 429 e 5xx, riusando ogni volta la stessa chiave. L’esempio cURL usa i nuovi tentativi integrati di curl, che coprono timeout, 429 e la maggior parte delle risposte 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');Risposte
| Stato | Quando | Cosa fare |
|---|---|---|
200 |
La prima richiesta riuscita, o una sua ripetizione entro 24 ore | Usa la risposta. Una ripetizione ha lo stesso corpo, compreso lo stesso id. |
400 Invalid Idempotency-Key |
La chiave è vuota, troppo lunga o contiene caratteri non validi | Correggi la chiave. |
409 Idempotency key in progress |
Una richiesta con la stessa chiave è ancora in elaborazione | Attendi un momento, poi ritenta con la stessa chiave. |
503 Idempotency unavailable |
Emailit non è riuscito a raggiungere l’archivio dell’idempotenza, quindi ha rifiutato la richiesta piuttosto che rischiare un duplicato | Ritenta con la stessa chiave. |
| Qualsiasi altro errore | La richiesta non è riuscita e la chiave è stata liberata | Risolvi la causa e ritenta con la stessa chiave. |
I limiti di frequenza vengono controllati prima della chiave, quindi un nuovo tentativo può comunque ricevere 429. Attendi quanto indicato dall’header retry-after e invia di nuovo la stessa chiave.