Guia prático
Requisições idempotentes
Use o cabeçalho Idempotency-Key para tentar de novo requisições de envio e de encaminhamento com segurança. Formato da chave, janela de 24 horas, respostas repetidas, respostas 409 e 503 e estratégias de chave.
Redes falham. Quando uma requisição de envio dá timeout, você não tem como saber se o Emailit a recebeu, e enviá-la de novo pode mandar o e-mail duas vezes para o seu cliente. Um cabeçalho Idempotency-Key torna a nova tentativa segura: o Emailit processa a primeira requisição e retorna a mesma resposta para qualquer repetição com a mesma chave.
Como funciona
Adicione um cabeçalho Idempotency-Key a POST /emails ou a POST /emails/{id}/forward.
- Primeira requisição. O Emailit reserva a chave para o seu workspace e processa a requisição.
- Sucesso. O Emailit armazena a resposta por 24 horas. Qualquer requisição com a mesma chave nessa janela recebe de volta a resposta armazenada com
200, e nenhum e-mail novo é criado. - Falha. Se a requisição falhar, por exemplo com
400ou402, o Emailit libera a chave. Corrija o problema e tente de novo com a mesma chave. - Sobreposição. Se uma segunda requisição chegar enquanto a primeira ainda está em andamento, ela recebe
409e nada é enviado. Tente de novo logo em seguida com a mesma chave.
As chaves valem por workspace, então dois workspaces podem usar a mesma chave sem conflito.
Formato da chave
| Regra | Valor |
|---|---|
| Comprimento | De 1 a 256 caracteres |
| Caracteres | Letras de A–Z e a–z, dígitos de 0–9, hífen - e sublinhado _ |
| Escopo | Por workspace |
| Janela | 24 horas após a primeira resposta bem-sucedida |
Uma chave com outros caracteres, como : ou /, é rejeitada com 400 Invalid Idempotency-Key.
Escolher uma chave
Derive a chave do evento que gera o e-mail, para que todos os caminhos de nova tentativa produzam a mesma chave:
| Exemplo de chave | |
|---|---|
| Recibo de pedido | order-1042-receipt |
| Redefinição de senha | password-reset-7f3c9a1e (o ID do token de redefinição) |
| Resumo semanal | digest-user-881-2026-w40 |
| Job em segundo plano | O ID do job, ou um UUID que você gera ao colocar o job na fila e guarda com ele |
Evite chaves que mudam entre as tentativas, como timestamps ou um UUID gerado dentro do loop de novas tentativas. Elas fazem cada nova tentativa parecer uma requisição nova.
Enviar com uma chave de idempotência
Os exemplos em Node.js, Python e PHP tentam de novo em caso de erros de rede e de respostas 409, 429 e 5xx, reutilizando sempre a mesma chave. O exemplo em cURL usa a repetição embutida do curl, que cobre timeouts, 429 e a maioria das respostas 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');Respostas
| Status | Quando | O que fazer |
|---|---|---|
200 |
Primeira requisição bem-sucedida, ou uma repetição dela em até 24 horas | Use a resposta. Uma repetição tem o mesmo corpo, incluindo o mesmo id. |
400 Invalid Idempotency-Key |
A chave está vazia, é longa demais ou tem caracteres inválidos | Corrija a chave. |
409 Idempotency key in progress |
Uma requisição com a mesma chave ainda está sendo processada | Espere um momento e tente de novo com a mesma chave. |
503 Idempotency unavailable |
O Emailit não conseguiu acessar o armazenamento de idempotência e recusou a requisição para não arriscar um envio duplicado | Tente de novo com a mesma chave. |
| Qualquer outro erro | A requisição falhou e a chave foi liberada | Corrija a causa e tente de novo com a mesma chave. |
Os limites de requisições são verificados antes da chave, então uma nova tentativa ainda pode receber 429. Espere o tempo do cabeçalho retry-after e envie a mesma chave de novo.