# Idempotente Anfragen

> Wiederholen Sie Sende- und Weiterleitungsanfragen sicher mit dem Header Idempotency-Key. Schlüsselformat, das 24-Stunden-Fenster, Wiederholungen, die Antworten 409 und 503 sowie Strategien für Schlüssel.

Netzwerke fallen aus. Wenn eine Sendeanfrage in ein Timeout läuft, wissen Sie nicht, ob Emailit sie erhalten hat, und ein erneutes Senden könnte Ihrem Kunden die E-Mail zweimal zustellen. Ein Header `Idempotency-Key` macht die Wiederholung sicher: Emailit verarbeitet die erste Anfrage und gibt für jede Wiederholung mit demselben Schlüssel dieselbe Antwort zurück.

## So funktioniert es

Fügen Sie `POST /emails` oder `POST /emails/{id}/forward` einen Header `Idempotency-Key` hinzu.

1. **Erste Anfrage.** Emailit reserviert den Schlüssel für Ihren Workspace und verarbeitet die Anfrage.
2. **Erfolg.** Emailit speichert die Antwort 24 Stunden lang. Jede Anfrage mit demselben Schlüssel in diesem Zeitfenster erhält die gespeicherte Antwort mit `200` zurück, und es wird keine neue E-Mail erstellt.
3. **Fehlschlag.** Schlägt die Anfrage fehl, zum Beispiel mit `400` oder `402`, gibt Emailit den Schlüssel wieder frei. Beheben Sie das Problem und wiederholen Sie die Anfrage mit demselben Schlüssel.
4. **Überschneidung.** Trifft eine zweite Anfrage ein, während die erste noch läuft, erhält sie `409`, und nichts wird gesendet. Wiederholen Sie die Anfrage kurz darauf mit demselben Schlüssel.

Schlüssel gelten pro Workspace, sodass zwei Workspaces denselben Schlüssel ohne Konflikt verwenden können.

> **Der Schlüssel identifiziert die Anfrage, nicht der Body:** Emailit vergleicht keine Anfrage-Bodys. Eine Wiederholung mit demselben Schlüssel gibt die ursprüngliche Antwort zurück, auch wenn der Body anders ist. Verwenden Sie für jede eigenständige E-Mail einen neuen Schlüssel.

## Schlüsselformat

| Regel | Wert |
| --- | --- |
| Länge | 1 bis 256 Zeichen |
| Zeichen | Buchstaben `A–Z` und `a–z`, Ziffern `0–9`, Bindestrich `-` und Unterstrich `_` |
| Gültigkeitsbereich | Pro Workspace |
| Zeitfenster | 24 Stunden nach der ersten erfolgreichen Antwort |

Ein Schlüssel mit anderen Zeichen, etwa `:` oder `/`, wird mit `400 Invalid Idempotency-Key` abgelehnt.

## Schlüssel wählen

Leiten Sie den Schlüssel aus dem Ereignis ab, das die E-Mail auslöst, damit jeder Wiederholungspfad denselben Schlüssel erzeugt:

| E-Mail | Beispielschlüssel |
| --- | --- |
| Bestellbeleg | `order-1042-receipt` |
| Passwort-Reset | `password-reset-7f3c9a1e` (die ID des Reset-Tokens) |
| Wöchentlicher Digest | `digest-user-881-2026-w40` |
| Hintergrundjob | Die ID des Jobs oder eine UUID, die Sie beim Einreihen des Jobs erzeugen und mit ihm speichern |

Vermeiden Sie Schlüssel, die sich zwischen Versuchen ändern, etwa Zeitstempel oder eine UUID, die in der Wiederholungsschleife erzeugt wird. Sie lassen jede Wiederholung wie eine neue Anfrage aussehen.

## Mit Idempotenzschlüssel senden

Die Beispiele für Node.js, Python und PHP wiederholen die Anfrage bei Netzwerkfehlern und bei den Antworten `409`, `429` und `5xx` und verwenden dabei jedes Mal denselben Schlüssel. Das cURL-Beispiel nutzt die eingebaute Wiederholung von curl, die Timeouts, `429` und die meisten `5xx`-Antworten abdeckt.

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

## Antworten

| Status | Wann | Was Sie tun |
| --- | --- | --- |
| `200` | Erste erfolgreiche Anfrage oder ihre Wiederholung innerhalb von 24 Stunden | Verwenden Sie die Antwort. Eine Wiederholung hat denselben Body, einschließlich derselben `id`. |
| `400 Invalid Idempotency-Key` | Der Schlüssel ist leer, zu lang oder enthält ungültige Zeichen | Korrigieren Sie den Schlüssel. |
| `409 Idempotency key in progress` | Eine Anfrage mit demselben Schlüssel wird noch verarbeitet | Warten Sie einen Moment und wiederholen Sie die Anfrage dann mit demselben Schlüssel. |
| `503 Idempotency unavailable` | Emailit konnte seinen Idempotenzspeicher nicht erreichen und hat die Anfrage abgelehnt, statt ein Duplikat zu riskieren | Wiederholen Sie die Anfrage mit demselben Schlüssel. |
| Jeder andere Fehler | Die Anfrage ist fehlgeschlagen, und der Schlüssel wurde freigegeben | Beheben Sie die Ursache und wiederholen Sie die Anfrage mit demselben Schlüssel. |

Rate Limits werden vor dem Schlüssel geprüft, daher kann eine Wiederholung trotzdem `429` erhalten. Warten Sie die im Header `retry-after` angegebene Zeit ab und senden Sie denselben Schlüssel erneut.

## Siehe auch

- [Idempotenz](/de/docs/api-reference/idempotency/) in der API-Referenz
- [E-Mail senden](/de/docs/email-api/send-email/)
- [E-Mail weiterleiten](/de/docs/email-api/retry-and-forward/)
- [Rate Limits](/de/docs/api-reference/rate-limits/)
- [Warum werden meine E-Mails doppelt gesendet?](/de/docs/kb/duplicate-emails-sent/)

---
Quelle: https://emailit.com/de/docs/email-api/idempotency/
