# 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`.

1. **Primeira requisição.** O Emailit reserva a chave para o seu workspace e processa a requisição.
2. **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.
3. **Falha.** Se a requisição falhar, por exemplo com `400` ou `402`, o Emailit libera a chave. Corrija o problema e tente de novo com a mesma chave.
4. **Sobreposição.** Se uma segunda requisição chegar enquanto a primeira ainda está em andamento, ela recebe `409` e 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.

> **É a chave, não o corpo, que identifica a requisição:** O Emailit não compara os corpos das requisições. Uma repetição com a mesma chave retorna a resposta original mesmo que o corpo seja diferente. Use uma chave nova para cada e-mail diferente.

## 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:

| E-mail | 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**

```bash
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."
  }'
```

**Node.js**

```javascript
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',
);
```

**Python**

```python
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",
)
```

**PHP**

```php
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.

## Veja também

- [Idempotência](/pt/docs/api-reference/idempotency/) na referência da API
- [Enviar um e-mail](/pt/docs/email-api/send-email/)
- [Encaminhar um e-mail](/pt/docs/email-api/retry-and-forward/)
- [Limites de requisições](/pt/docs/api-reference/rate-limits/)
- [Por que os meus e-mails são enviados duas vezes?](/pt/docs/kb/duplicate-emails-sent/)

---
Fonte: https://emailit.com/pt/docs/email-api/idempotency/
