Bei einer zuverlässigen E-Mail-API-Integration geht es vor allem darum, was passiert, wenn etwas schiefgeht. Der Normalfall ist ein einzelner HTTP-Request. Die schwierigen Fälle sind ein Timeout, bei dem Sie nicht wissen, ob die Nachricht angenommen wurde, ein Worker, der mitten in einem Batch abstürzt, und eine Retry-Schleife, die aus einem kurzen Ausfall eine Flut macht. Dieser Leitfaden gibt uns eine Reihe von Mustern an die Hand, die dafür sorgen, dass jede E-Mail genau einmal versendet wird, oder im schlimmsten Fall vorhersehbar einmal, egal welchen Provider-Endpunkt Sie aufrufen.
Wir beschreiben die Muster bewusst allgemein, damit sie mit jeder HTTP-E-Mail-API funktionieren, einschließlich MigoSMTP. Wo eine bestimmte Funktion wichtig ist, etwa ein Idempotency-Header, ein Batch-Endpunkt oder ein Ratenlimit, prüfen Sie die genauen Details in Ihrem MigoSMTP-Konto oder der API-Dokumentation, statt sie vorauszusetzen. Wenn Sie sich noch zwischen den Protokollen entscheiden, lesen Sie zuerst SMTP oder API: So treffen Sie die Wahl.
Warum E-Mail-Versand auf Arten scheitert, die andere API-Aufrufe nicht kennen
Die meisten API-Aufrufe lassen sich ohne Schaden wiederholen. E-Mail nicht. Ein zweiter Aufruf zum Anlegen eines Nutzers scheitert meist mit einem Konflikt, ein zweiter Aufruf zum Senden einer Quittung versendet dagegen munter eine zweite Quittung. Der Kunde sieht zwei Nachrichten, der Support sieht eine Beschwerde, und ein doppelt gesendeter Passwort-Reset kann den ersten Code ungültig machen, während die Person ihn gerade liest.
Das Kernproblem ist das unbekannte Ergebnis. Ihr Client sendet die Anfrage, der Server nimmt sie an, und die Antwort geht durch einen Netzwerkabbruch oder Timeout verloren. Aus Ihrer Sicht sieht das identisch aus mit einer Anfrage, die nie angekommen ist. Wenn Sie wiederholen, erzeugen Sie womöglich ein Duplikat. Wenn Sie nicht wiederholen, verlieren Sie womöglich die Nachricht. Jedes der folgenden Muster dient dazu, diese Mehrdeutigkeit aufzulösen.
| Fehler | Was Sie wissen | Risiko bei naiver Behandlung |
|---|---|---|
| Verbindung abgelehnt oder DNS-Fehler | Die Anfrage hat die API nie erreicht | Retry ist sicher; das Risiko ist nur ein Retry-Sturm |
| Lese-Timeout nach dem Senden der Anfrage | Nichts über die Annahme | Retry ohne Dedupe-Key kann doppelt senden |
| 429 Too Many Requests | Die API hat den Aufruf aus Gründen der Taktung abgelehnt | Sofortiger Retry verschärft den Druck |
| 5xx von der API oder einem Gateway | Meist nicht angenommen, aber nicht garantiert | In manchen Fällen dieselbe Mehrdeutigkeit wie bei einem Timeout |
| 400- oder 422-Validierungsfehler | Die Anfrage selbst ist falsch | Endloses Wiederholen verschwendet Kapazität und verdeckt einen Fehler |
| Worker-Absturz nach dem API-Aufruf | Angenommen, aber nicht von Ihnen erfasst | Der Job wird erneut zugestellt und die E-Mail wird nochmals gesendet |
Idempotency-Keys und Vermeidung doppelter Sendungen
Eine idempotente Operation liefert dasselbe Ergebnis, egal wie oft Sie sie ausführen. Viele HTTP-APIs erlauben es, einen Idempotency-Key mitzugeben, üblicherweise in einem Header, sodass eine wiederholte Anfrage mit demselben Key das ursprüngliche Ergebnis zurückgibt, statt eine neue Ressource zu erzeugen. Die IETF arbeitet an einem aktiven Entwurf für ein standardisiertes Idempotency-Key-Header-Feld, was zeigt, wie verbreitet die Idee ist. Ob MigoSMTP einen solchen Header unterstützt und wie lange ein Key gespeichert bleibt, muss in der MigoSMTP-API-Dokumentation bestätigt werden. Wir setzen das hier nicht voraus.
Die gute Nachricht: Sie müssen nicht auf Serverunterstützung warten. Dieselbe Garantie können Sie auf Ihrer Seite mit einer Dedupe-Tabelle aufbauen. Der Kerngedanke ist, dass Ihre Anwendung die Identität einer E-Mail festlegt, bevor sie mit dem Netzwerk spricht.
Einen deterministischen Dedupe-Key wählen
Ein guter Key wird aus dem Geschäftsereignis abgeleitet und nicht erst beim Senden erzeugt. Verwenden Sie etwas wie order-1042-shipped oder user-77-password-reset-2026-10-15T09:14. Eine zufällige UUID, die im Worker erzeugt wird, hilft nicht, denn ein erneut zugestellter Job würde eine neue erzeugen. Nehmen Sie den Nachrichtentyp auf, damit eine Quittung und eine Versandbenachrichtigung zur selben Bestellung nicht kollidieren, und fügen Sie einen Zeitabschnitt oder Zähler nur dann hinzu, wenn wiederholte Sendungen legitim sind, zum Beispiel bei einer täglichen Zusammenfassung.
Per Unique Constraint durchsetzen
Anwendungsprüfungen wie erst abfragen, dann einfügen sind anfällig für Race Conditions. Legen Sie einen Unique Index auf den Key und lassen Sie die Datenbank Gleichstände entscheiden. Der erste Schreiber gewinnt und legt den Sendedatensatz an. Jeder spätere Versuch erhält entweder einen Constraint-Fehler oder findet die vorhandene Zeile und liest ihren Status.
CREATE TABLE email_sends (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
dedupe_key VARCHAR(190) NOT NULL,
to_address VARCHAR(320) NOT NULL,
template VARCHAR(80) NOT NULL,
payload_json JSON NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
attempts INT NOT NULL DEFAULT 0,
next_try_at DATETIME NULL,
message_id VARCHAR(255) NULL,
last_error TEXT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
UNIQUE KEY uq_dedupe (dedupe_key)
);
Wir verwenden die Status pending, sending, accepted, failed und unknown. Accepted bedeutet, dass die API Erfolg zurückgegeben hat und Sie die Message-ID gespeichert haben. Unknown bedeutet, dass ein Aufruf erfolgt ist und das Ergebnis nicht sicher ist, was wir im Abschnitt zu Retries behandeln. Wenn die API einen Idempotency-Key unterstützt, übergeben Sie denselben Dedupe-Key auch dort, damit beide Ebenen übereinstimmen. Betrachten Sie serverseitige Unterstützung als zweites Sicherheitsnetz, nicht als Ersatz.
Das Outbox-Muster: nur senden, was committet wurde
Der klassische Fehler sieht so aus: Ihr Code speichert eine Bestellung, ruft dann die E-Mail-API auf und kehrt zurück. Schlägt der API-Aufruf fehl, existiert die Bestellung ohne E-Mail. Rufen Sie zuerst die API auf und der Datenbank-Commit scheitert, erhält der Kunde eine Bestätigung für eine Bestellung, die es nicht gibt. Zwei Systeme lassen sich nicht in einer Transaktion aktualisieren, also ändern wir das Problem.
Beim Outbox-Muster ist die E-Mail-Anfrage nur eine weitere Zeile, die in derselben Transaktion wie die fachliche Änderung geschrieben wird. Entweder werden beide committet oder keine. Ein separater Prozess liefert die Outbox-Zeilen dann an die API aus. Die Zustellung wird zu einem wiederholbaren Hintergrundjob, und die Datenbank ist die einzige Quelle der Wahrheit darüber, was gesendet werden muss.
- Transaktion: Fügen Sie die Bestellung und die email_sends-Zeile gemeinsam mit dem Status pending ein.
- Commit: Erst nach dem Commit wird etwas für den Sender-Prozess sichtbar.
- Claim: Ein Worker wählt fällige pending-Zeilen aus und markiert sie als sending, mit Zeilensperren, damit nie zwei Worker dieselbe Zeile übernehmen.
- API aufrufen: Senden Sie die Anfrage und erfassen Sie dann das Ergebnis und die Message-ID in der Zeile.
- Abgleichen: Ein periodischer Job sucht nach Zeilen, die in sending oder unknown hängen, und löst sie auf.
Den Worker können Sie durch Polling der Tabelle alle paar Sekunden oder durch Veröffentlichen in einer Queue nach dem Commit versorgen. Polling ist einfacher und hat weniger bewegliche Teile. Eine Queue hilft bei hohem Volumen, doch behalten Sie die Tabelle als maßgebliche Aufzeichnung, denn Queues können Nachrichten erneut zustellen oder verlieren, und die Tabelle lässt Sie fragen, was mit dieser E-Mail passiert ist.
Queueing in Ihrer App: Worker, Parallelität und Reihenfolge
Rufen Sie die E-Mail-API nicht innerhalb des Web-Requests auf, es sei denn, Sie haben einen triftigen Grund. Eine langsame Antwort eines Drittanbieters wird sonst zu einem langsamen Checkout, und eine Spitze bei den Registrierungen wird zu einer Spitze bei offenen Verbindungen. Kehren Sie zum Nutzer zurück, sobald die Outbox-Zeile committet ist, und lassen Sie Hintergrund-Worker die Zustellung übernehmen.
- Parallelität begrenzen. Eine feste Anzahl von Workern, jeweils mit einem kleinen HTTP-Connection-Pool, ergibt eine vorhersehbare Obergrenze für Requests pro Sekunde. Unbegrenzte Parallelität ist der Weg, auf dem eine App versehentlich ein API-Limit erreicht.
- Queues nach Dringlichkeit trennen. Passwort-Resets und Einmalcodes sollten nie hinter einem Newsletter warten. Verwenden Sie getrennte Queues oder Prioritäten, wie in Trennung von Transaktions- und Marketing-Streams beschrieben.
- Jobs idempotent machen. Gehen Sie davon aus, dass jeder Job zweimal zugestellt werden kann. Der erste Schritt eines Jobs ist, die Zeile zu laden und zu beenden, wenn der Status bereits accepted ist.
- Sich nicht auf die Reihenfolge verlassen. Retries und parallele Worker vertauschen Nachrichten. Wenn die Reihenfolge wichtig ist, zum Beispiel Verifizierung vor Willkommensmail, bilden Sie sie als Abhängigkeit in Ihren Daten ab, nicht als Queue-Reihenfolge.
- Veraltete Mails verfallen lassen. Ein Login-Code, der nach zwanzig Minuten zugestellt wird, ist schlimmer als keiner. Geben Sie zeitkritischen Zeilen ein Ablaufdatum und markieren Sie sie als abgelaufen, statt sie zu senden.
Halten Sie Payloads klein. Speichern Sie den Template-Namen und die Variablen oder den fertig gerenderten Inhalt, aber keine großen Anhänge in Queue-Nachrichten. Verweisen Sie auf gespeicherte Dateien per ID, entsprechend den Limits in unserem Leitfaden zu Größenlimits und MIME.
Timeouts und Klassifizierung von Retries
Jeder HTTP-Aufruf braucht explizite Timeouts. Ein Client ohne Timeout wartet weit länger, als es einen Menschen interessiert, und blockiert die ganze Zeit einen Worker-Slot. Setzen Sie einen kurzen Connect-Timeout, oft ein bis zwei Sekunden, und einen Gesamt-Timeout, der für eine normale Antwort lang genug und für ein schnelles Scheitern kurz genug ist. Wählen Sie die Werte anhand Ihrer eigenen gemessenen Latenz und der Hinweise in Ihrer MigoSMTP-API-Dokumentation, nicht anhand einer Zahl aus einem Blogbeitrag.
Ordnen Sie das Ergebnis eines Aufrufs nach dessen Ende einer von drei Kategorien zu. Diese Klassifizierung ist das Herzstück der Integration.
| Ergebnis | Beispiele | Aktion |
|---|---|---|
| Angenommen | 2xx mit einer Message-ID | ID speichern, als accepted markieren, beenden |
| Definitiv abgelehnt, nicht wiederholen | 400, 401, 403, 404, 422, andere 4xx, die Ihre Anfrage beschreiben | Als failed markieren, Fehlerinhalt erfassen, bei Wiederholung alarmieren; Code, Zugangsdaten oder Daten korrigieren |
| Später wiederholen, gleicher Dedupe-Key | 429, 500, 502, 503, 504, Verbindungsfehler, DNS-Fehler | Mit Backoff wiederholen; Retry-After beachten |
| Unbekanntes Ergebnis | Lese-Timeout, Verbindungsabbruch nach dem Senden | Nur mit demselben Dedupe-Key wiederholen oder zuerst abgleichen |
Die HTTP-Semantik dieser Codes, einschließlich des Retry-After-Headers, ist in RFC 9110 definiert, und RFC 6585 definiert 429. Beachten Sie den Unterschied zu SMTP. Bei SMTP sind 4xx-Antworten temporär und 5xx dauerhaft, was wir in unserem Leitfaden zu SMTP-Bounce-Codes behandeln. Bei einer HTTP-API sind die Rollen anders: 5xx bedeutet, dass der Dienst ein Problem hatte, und 4xx, dass Ihre Anfrage eines hatte. Vermischen Sie die beiden Modelle nicht. Ein HTTP 400 ist niemals ein temporärer Mailfehler.
Backoff mit Jitter
Retry-Verzögerungen sollten exponentiell wachsen und Zufall enthalten, damit tausend Worker, die gemeinsam gescheitert sind, nicht alle gemeinsam zurückkommen. Ein gängiger Ansatz ist eine Basisverzögerung, die bei jedem Versuch verdoppelt, auf ein Maximum begrenzt und mit einem zufälligen Anteil versehen wird. Enthält die Antwort Retry-After, verwenden Sie den Wert als Minimum. Legen Sie eine maximale Anzahl von Versuchen oder ein maximales Alter fest, danach wechselt die Zeile zu failed und löst einen Alarm aus. Unser Leitfaden zu Ratenlimits, Retries und Backoff geht tiefer auf die Strategie ein.
import random, time
RETRY_STATUS = {429, 500, 502, 503, 504}
def next_delay(attempt, retry_after=None, base=2.0, cap=300.0):
delay = min(cap, base * (2 ** attempt))
delay = random.uniform(delay / 2, delay)
if retry_after is not None:
delay = max(delay, float(retry_after))
return delay
def deliver(row, client):
try:
resp = client.post("/send", json=row.payload,
headers={"X-Dedupe-Key": row.dedupe_key},
timeout=(2, 15))
except (ConnectionError, TimeoutError):
return ("retry", next_delay(row.attempts))
if 200 <= resp.status_code < 300:
return ("accepted", resp.json().get("id"))
if resp.status_code in RETRY_STATUS:
ra = resp.headers.get("Retry-After")
return ("retry", next_delay(row.attempts, ra))
return ("failed", resp.text[:500])
Der Header-Name in diesem Beispiel ist zur Veranschaulichung erfunden. Verwenden Sie, was die MigoSMTP-API-Dokumentation vorgibt, oder verlassen Sie sich allein auf Ihre eigene Tabelle, wenn es keinen solchen Header gibt.
Antwortbehandlung und Speichern von Message-IDs
Eine Erfolgsantwort ist nicht das Ende der Geschichte, sondern der Beginn der Nachverfolgung. Speichern Sie die Message-ID aus der Antwort sofort im Sendedatensatz, im selben Schritt, in dem Sie die Zeile als accepted markieren. Dann lassen sich Webhook-Ereignisse zu Zustellung, Bounce und Beschwerde damit verknüpfen. Die Zuverlässigkeit dieser zweiten Hälfte behandelt E-Mail-Webhooks zuverlässig verarbeiten.
- Bewahren Sie den rohen API-Antwortinhalt für kurze Zeit auf. Beim Debugging ist er unschätzbar.
- Erfassen Sie Zeitstempel für Erstellung, ersten Versuch, Annahme und letztes Ereignis. Lücken dazwischen zeigen, wo Verzögerungen entstehen.
- Verstehen Sie, was „angenommen" bedeutet. Eine 2xx-Antwort einer E-Mail-API bedeutet normalerweise, dass der Dienst die Nachricht zur Zustellung eingereiht hat, nicht dass der Server des Empfängers sie akzeptiert hat. Die endgültige Zustellung wird später per Ereignis gemeldet.
- Parsen Sie Fehlermeldungen nicht als Logik. Verwenden Sie Statuscodes und dokumentierte Fehlercodes, und behandeln Sie Freitext als Information für Menschen.
- Indizieren Sie die Spalte mit der Message-ID, damit der Support mit einer Abfrage von einer ID im Log zu einem Kundendatensatz gelangt.
Schwärzen Sie Geheimnisse und personenbezogene Daten in Logs. Protokollieren Sie Dedupe-Key, Send-ID, Status und Message-ID, aber keine API-Keys oder vollständigen Nachrichtentexte. Keys sollten gespeichert und rotiert werden, wie in der API-Key-Sicherheit beschrieben.
Batching und Massenversand
Der Versand an viele Empfänger ist eine andere Arbeitslast als eine einzelne Quittung. Sie verwalten nun Durchsatz, Speicher und Fairness gegenüber anderem Traffic. Manche E-Mail-APIs bieten einen Batch-Endpunkt, der viele Nachrichten in einer Anfrage annimmt, andere erwarten eine Nachricht pro Anfrage mit vielen parallelen Aufrufen. Prüfen Sie in Ihrem MigoSMTP-Konto oder der API-Dokumentation, was verfügbar ist und welche Empfängerlimits pro Anfrage gelten, bevor Sie Ihr Design auf eines von beiden ausrichten.
- Zielgruppe in Blöcke teilen. Lesen Sie Empfänger seitenweise aus der Datenbank, nicht als eine riesige Liste im Speicher. Erzeugen Sie Sendezeilen pro Empfänger und lassen Sie dann Worker sie abarbeiten.
- Pro Empfänger personalisieren. Vermeiden Sie viele Empfänger in To oder Cc. Jede Person sollte ihre eigene Nachricht erhalten, mit eigenem Abmeldelink und eigenen Tracking-Daten.
- Den Versand takten. Verteilen Sie große Kampagnen über ein Zeitfenster, statt alles auf einmal freizugeben. Die Taktung schützt auch Ihre Reputation, besonders bei neuen Domains, wie im Warm-up-Leitfaden beschrieben.
- Die Kampagne wiederaufnehmbar machen. Weil jeder Empfänger eine Zeile mit eindeutigem Dedupe-Key wie campaign-318-user-9001 hat, können Sie einen Versand jederzeit anhalten und neu starten, ohne doppelt zu senden.
- Teilausfälle behandeln. Wenn ein Batch-Aufruf für manche Nachrichten gelingen und für andere scheitern kann, speichern Sie Ergebnisse pro Nachricht. Markieren Sie nie einen ganzen Batch anhand eines einzelnen Statuscodes als angenommen oder fehlgeschlagen, es sei denn, die Dokumentation sagt, dass es so funktioniert.
Listengrundlagen nicht überspringen: Massenversand über eine API ändert nichts an den Regeln für Einwilligung, Suppression und Abmeldung. Filtern Sie unterdrückte Adressen, bevor Sie Sendezeilen anlegen, nach dem Vorgehen in unserem Leitfaden zur Suppression-Liste.
Praxisbeispiel: ein Ausfall, zwei Designs
Hier ein erfundenes Beispiel mit runden Zahlen. Ein Onlineshop sendet für jede Bestellung eine Bestellbestätigung. An einem geschäftigen Nachmittag entstehen 6.000 Bestellungen in einer Stunde, und die E-Mail-API hat 4 Minuten lang langsame Antworten und sporadische 503-Fehler.
| Schritt | Naives Design | Outbox mit Dedupe-Key |
|---|---|---|
| Bestellung angelegt | Der Request ruft die API direkt auf | Der Request schreibt Bestellung und Sendezeile und kehrt sofort zurück |
| API 4 Minuten langsam | Der Checkout wartet auf einen langen Timeout, manche Kunden sehen Fehler und klicken erneut auf Bezahlen | Checkout unbeeinträchtigt; Zeilen warten in pending |
| Timeout bei 150 Aufrufen | Der Code wiederholt sofort; viele dieser Nachrichten waren bereits angenommen | Zeilen als unknown markiert; später mit demselben Dedupe-Key wiederholt |
| Ergebnis | 150 Bestellungen mit grob je 2 Bestätigungen, dazu doppelte Bestellungen durch Doppelklicks | Etwa 150 um einige Minuten verzögerte Nachrichten; keine Duplikate |
| Auswirkung auf den Support | Dutzende Tickets mit der Frage, welche E-Mail korrekt ist | Keine; der Alarm zur Queue-Wartezeit wurde ausgelöst und wieder aufgehoben |
Bei 6.000 Bestellungen pro Stunde liegt der Durchschnitt bei 100 pro Minute, sodass eine 4-minütige Verlangsamung einen Rückstau von etwa 400 Zeilen hinterlässt. Wenn die Worker nach der Erholung der API 300 pro Minute senden können, ist der Rückstau in etwa 2 Minuten abgebaut (400 Zeilen geteilt durch die 200 pro Minute an freier Kapazität über die 100 pro Minute neuer Zugänge hinaus). Diese Rechnung lohnt sich für Ihr eigenes System: Die freie Kapazität Ihrer Worker entscheidet, wie schnell Sie sich erholen, und die Begrenzung der Parallelität entscheidet, ob die Erholung innerhalb der Limits Ihres Kontos bleibt.
Bewusstsein für Ratenlimits
Ratenlimits gibt es in Formen wie Requests pro Sekunde, Nachrichten pro Tag, gleichzeitige Verbindungen und Empfänger pro Request. Welche für Ihr Konto gelten und welche genauen Werte, steht in Ihrem MigoSMTP-Konto oder der API-Dokumentation. Raten Sie nicht. Bauen Sie Ihren Client so, dass die Zahlen Konfiguration sind und keine Konstanten im Code.
- Verwenden Sie einen Token Bucket oder eine feste Pause zwischen Requests in jedem Worker, mit einer globalen Obergrenze über alle Worker.
- Stoppen Sie bei 429 den Versand aus diesem Worker für die Dauer von Retry-After oder, falls keine angegeben ist, für eine Backoff-Verzögerung. Wiederholen Sie nicht einfach denselben Aufruf.
- Achten Sie auf langsamen Erfolg. Ein steigender Latenztrend kommt oft vor den Fehlern und ist ein Signal, die Parallelität zu verringern.
- Verwenden Sie einen Circuit Breaker. Pausieren Sie nach einer Serie von Fehlern die Aufrufe für ein kurzes Fenster und testen Sie dann mit einem Request, bevor Sie mit voller Geschwindigkeit fortfahren.
- Behalten Sie die Limits der Empfängerseite im Blick. Die Annahme durch den Provider ist nur der erste Hop. Mailbox-Provider wenden eigene Limits an, die in unserem Leitfaden zu den Anforderungen für Massenversender behandelt werden.
Graceful Degradation bei Ausfall der API
Jeder externe Dienst ist zeitweise nicht verfügbar. Entscheiden Sie im Voraus, was Ihr Produkt dann tut, damit die Antwort nicht mitten im Störfall improvisiert wird.
| E-Mail-Typ | Empfohlenes Verhalten bei einem Ausfall |
|---|---|
| Passwort-Reset, Einmalcode | Mit kurzem Ablauf einreihen; auf dem Bildschirm anzeigen, dass die Nachricht verzögert sein kann, und nach einer Wartezeit erneutes Senden anbieten |
| Bestell- oder Zahlungsbestätigung | Einreihen und verspätet zustellen; die Bestätigung zusätzlich auf der Bestellseite anzeigen |
| Konto-Benachrichtigungen | Einreihen und verspätet zustellen; erwägen, mehrere zu einer zusammenzufassen |
| Newsletter und Kampagnen | Die Kampagne pausieren und später fortsetzen; nie versuchen, hastig aufzuholen |
| Interne Alarme | Auf einen anderen Kanal wie Chat oder einen zweiten Provider ausweichen |
Richten Sie Alarme für die Queue-Tiefe und das Alter der ältesten wartenden Zeile ein, nicht nur für Fehlerzahlen. Eine wachsende Queue ohne Fehler bedeutet trotzdem, dass Kunden warten. Setzen Sie eine Obergrenze für die Queue, damit Speicher nicht unbegrenzt wachsen kann, und legen Sie fest, was zuerst verworfen wird, wenn die Grenze erreicht ist. Wenn Sie einen zweiten Provider als Fallback einplanen, denken Sie an den Aufwand für Warm-up und Authentifizierung eines zweiten Pfads, wie in unserem Migrationsleitfaden beschrieben.
Die Integration testen
Tests, die nur eine 200-Antwort prüfen, schützen Sie nicht. Entscheidend ist das Verhalten im Fehlerfall, also machen Sie Fehler leicht erzeugbar. Führen Sie Ihren Client gegen einen Mock-Server oder eine Sandbox aus, die sich anweisen lässt, langsam zu antworten, 429 mit Retry-After oder 503 zurückzugeben, die Verbindung nach dem Lesen der Anfrage zu trennen oder einen fehlerhaften Body zu liefern. Nutzen Sie einen Mail-Catcher für Inhaltsprüfungen, gemäß unserem Leitfaden zur Staging-Umgebung.
- Test für doppelte Jobs: Stellen Sie denselben Job zweimal in die Queue und prüfen Sie, dass nur ein Request die Mock-API erreicht.
- Crash-Test: Beenden Sie einen Worker nach dem API-Aufruf und vor der Datenbankaktualisierung und prüfen Sie, dass der Reconciler die Zeile ohne zweite Sendung auflöst.
- Timeout-Test: Lassen Sie den Mock die Anfrage annehmen und dann hängen, und prüfen Sie, dass die Zeile genau einmal als accepted endet.
- Poison-Message-Test: Senden Sie eine Payload, die 422 zurückgibt, und bestätigen Sie, dass sie nach einem Versuch mit gespeichertem Fehler zu failed wechselt.
- Lasttest: Fahren Sie einen realistischen Burst gegen das Staging, um zu bestätigen, dass Parallelitätsgrenzen und Backoff wie geplant funktionieren.
Checkliste vor dem Livegang
- Jede E-Mail hat einen deterministischen Dedupe-Key, und ein Unique Index erzwingt ihn.
- Sendezeilen werden in derselben Transaktion wie das Geschäftsereignis geschrieben (Outbox).
- Die Unterstützung eines Idempotency-Headers ist in der MigoSMTP-API-Dokumentation bestätigt, und unsere eigene Tabelle wird trotzdem verwendet.
- Worker, nicht Web-Requests, rufen die API auf, mit begrenzter Parallelität.
- Connect- und Gesamt-Timeouts sind bei jedem Aufruf gesetzt.
- Antworten werden klassifiziert: accepted, retry, failed, unknown. Andere 4xx-Antworten werden nie blind wiederholt.
- Backoff hat Jitter, eine Obergrenze, eine maximale Versuchszahl und beachtet Retry-After.
- Message-IDs werden in den Sendezeilen gespeichert und indiziert.
- Massenversand ist in Blöcke geteilt, getaktet und wiederaufnehmbar, mit Suppression-Filterung vor der Zeilenerzeugung.
- Ratenlimits sind Konfiguration und stammen aus Ihrem Konto oder der API-Dokumentation.
- Alarme existieren für das Alter der ältesten wartenden Zeile, die Zahl fehlgeschlagener Sendungen und die 429-Rate.
- Fehlertests für Duplikate, Abstürze, Timeouts und 429 bestehen im Staging.
Möchten Sie eine zweite Meinung zu Ihrem Sendedesign? Schildern Sie uns, wie Ihre Anwendung heute E-Mails versendet und wo Sie Retries oder Duplikate beunruhigen, und wir helfen Ihnen bei der Planung einer sicheren Integration.
Weiterführende Literatur und Quellen
Häufig gestellte Fragen
Was ist ein Idempotency-Key bei einer E-Mail-API?
Das ist ein eindeutiger Wert, den Sie an eine Anfrage anhängen, damit der Server eine Wiederholung derselben Anfrage erkennt und die Arbeit nicht doppelt ausführt. Bei E-Mails bedeutet das: Ein Retry nach einem Timeout erzeugt keine zweite Kopie der Nachricht. Ob und wie MigoSMTP einen Idempotency-Header unterstützt, muss in der MigoSMTP-API-Dokumentation bestätigt werden. In jedem Fall erreichen Sie denselben Schutz mit einer eigenen Dedupe-Tabelle.
Brauche ich trotzdem eine eigene Dedupe-Tabelle, wenn die API Idempotenz unterstützt?
Ja, das empfehlen wir. Serverseitige Idempotenz deckt meist nur ein begrenztes Zeitfenster und nur Anfragen ab, die die API erreichen. Ihre eigene Tabelle verhindert außerdem doppelte Jobs, doppelte Webhook-Auslöser und Doppelklicks weiter vorgelagert und liefert Ihnen einen Audit-Trail dessen, was Sie senden wollten.
Ist ein Retry nach einem Timeout sicher?
Nur wenn Sie eine Dedupe-Strategie haben. Ein Timeout bedeutet, dass Sie nicht wissen, ob die API die Nachricht angenommen hat. Ohne Idempotency-Key oder eine Abfrage über Ihre eigene Referenz kann ein Retry die E-Mail doppelt versenden. Mit einem solchen Schutz ist ein Retry sicher.
Bei welchen HTTP-Statuscodes sollte ich es erneut versuchen?
Wiederholen Sie bei Netzwerkfehlern, Timeouts, 429 Too Many Requests und 5xx-Serverfehlern, mit exponentiellem Backoff samt Jitter und unter Beachtung eines eventuellen Retry-After-Headers. Wiederholen Sie andere 4xx-Fehler wie 400, 401, 403 oder 422 nicht, denn dieselbe Anfrage scheitert genauso, bis Sie etwas ändern.
Was ist das Outbox-Muster?
Sie schreiben die E-Mail-Anfrage in derselben Datenbanktransaktion wie die fachliche Änderung in eine Tabelle, und ein separater Prozess liest die noch nicht gesendeten Zeilen und ruft die API auf. So entfällt die Lücke, in der der Datenbank-Commit gelingt, der E-Mail-Aufruf aber scheitert, oder umgekehrt.
Sollte ich E-Mails aus dem Web-Request oder aus einer Queue senden?
Für fast alles aus einer Queue. Eine Queue schützt die Latenz für Nutzer, ermöglicht sichere Retries und lässt Sie die Rate steuern. Die Ausnahme ist ein sehr einfaches internes Tool, bei dem ein fehlgeschlagener Versand einfach der Person angezeigt werden kann, die ihn ausgelöst hat.
Wie sende ich eine große Kampagne über eine API?
Teilen Sie die Empfänger in Batches auf, begrenzen Sie die Parallelität, beachten Sie dokumentierte Limits und Retry-After und verteilen Sie den Versand über die Zeit. Genaue Limits und ob es einen Batch-Endpunkt gibt, entnehmen Sie Ihrem MigoSMTP-Konto oder der API-Dokumentation. Bei neuen Domains oder IPs sollten Sie zusätzlich einem Warm-up-Plan folgen.
Warum sollte ich die von der API zurückgegebene Message-ID speichern?
Sie ist der Verknüpfungsschlüssel zwischen Ihrem Sendedatensatz und späteren Ereignissen wie Zustell-, Bounce- und Beschwerde-Webhooks. Ohne sie wird die Support-Frage „Hat dieser Kunde die Quittung erhalten?" zum Ratespiel.
Wie teste ich eine E-Mail-Integration, ohne echte Mails zu versenden?
Führen Sie den API-Client gegen einen Mock-Server oder eine Sandbox aus, die auf Abruf langsame Antworten, 429 und 5xx liefern kann, und nutzen Sie einen Mail-Catcher für Inhaltsprüfungen. Prüfen Sie Ihre Sendetabelle, nicht nur die HTTP-Aufrufe. Unser Staging-Leitfaden beschreibt sichere Setups.