Doppelte E-Mails: doppelte Auslöser finden und Dedupe-Keys hinzufügen
Doppelte E-Mails in Produktion entstehen durch doppelte Auslöser, Retries oder Job-Overlap. Erfahre, wie du die Ursache findest und Dedupe-Keys hinzufügst, damit nur eine E-Mail gesendet wird.

Was „doppelte E-Mails“ in der Produktion wirklich bedeutet
Nutzer melden selten „doppelte E-Mails“ als technischen Fehler. Sie berichten das Gefühl: „Ich habe zwei Passwort-Reset-E-Mails bekommen“, „Meine Quittung kam zweimal an“ oder „Eure App spammt mich“. Manchmal sind die Kopien identisch. Manchmal unterscheiden sie sich um ein paar Sekunden, die Betreffzeile oder ein Trackingpixel, was es schwerer macht, das Geschehen zu beweisen.
Duplikate zerstören Vertrauen. Wenn eine Quittung zweimal ankommt, befürchten Leute, dass sie doppelt belastet wurden. Wenn eine Login- oder Passwort-Reset-E-Mail dupliziert wird, sorgen sich Nutzer, jemand würde an ihrem Account herumstoßen. Intern erzeugen Duplikate Support-Tickets, laute Alerts und verfälschte Metriken. Mit der Zeit können sie auch die Zustellbarkeit verschlechtern, weil Mail-Provider Bursts und wiederholte Inhalte bemerken.
Duplikate sind knifflig, weil „eine E-Mail senden“ selten ein einziger Schritt ist. Dasselbe Business-Ereignis kann sich über Systeme ausbreiten: ein Webhook feuert, ein Hintergrundjob retried, ein Queue-Worker startet neu, oder ein Nutzer klickt zweimal und dein Frontend sendet zweimal. Jedes Teil kann „richtig“ arbeiten, aber zusammen lösen sie dieselbe Sendung mehrmals aus.
Das Ziel ist einfach und testbar: ein Business-Ereignis = eine E-Mail.
Ein Business-Ereignis ist das, was dir wichtig ist, z. B. „Passwort-Reset angefordert für Nutzer 123“ oder „Rechnung 987 wurde bezahlt“. Sobald du dieses Ereignis definierst, schütze es mit einer eindeutigen Identität, sodass jede Schicht sagen kann: „Das wurde bereits gesendet."
Eine praktische Einordnung:
- Ein Duplikat ist nicht „zwei SMTP-Aufrufe“. Es ist „dasselbe Ereignis hat zwei Nachrichten erzeugt“.
- Es geht nicht nur darum, Retries zu verringern. Es geht darum, jeden Auslöser so sicher zu machen, dass er zweimal laufen kann.
- Das beste Ergebnis ist langweilig: Retries, Webhooks und Neustarts passieren, und Nutzer bekommen trotzdem nur eine E-Mail.
Häufige Ursachen: doppelte Auslöser, Retries und Job-Overlap
Die meisten Duplikate entstehen nicht, weil „der E-Mail-Dienst verrückt geworden ist“. Sie passieren, weil deine App dieselbe Send-Anfrage mehr als einmal stellt, oft von zwei Stellen, die nichts voneinander wissen.
Ein typisches Muster beginnt am Rand. Ein Nutzer klickt doppelt, ein Formular wird zweimal abgeschickt oder das Frontend wiederholt, weil es keine Antwort bekam. Wenn das Backend jede Anfrage als neues Business-Ereignis behandelt, hast du zwei Sends erzeugt.
Webhooks sind eine weitere häufige Quelle. Viele Anbieter liefern denselben Webhook absichtlich mehrmals, besonders wenn dein Endpoint langsam ist oder einen Nicht-2xx-Status zurückgibt. Wenn du jede Zustellung als neu behandelst, kannst du dieselbe „E-Mail senden“-Aktion erneut auslösen.
Hintergrundjobs bringen ihre eigene Art von Duplikation. Ein Job kann wegen Rennen (zwei Server bearbeiten dieselbe Anfrage) doppelt in die Queue gelangen, wegen Replays (eine Queue fährt eine Nachricht neu an) oder weil ein Worker nach einem Timeout erneut versucht. Am schlimmsten ist, wenn der Worker nach dem Akzeptieren durch den E-Mail-Provider timeouts hat und dann nochmals sendet.
Wenn du ein einzelnes Duplikat nachverfolgst, findest du gewöhnlich eines davon:
- Dasselbe Ereignis wurde zweimal erstellt (Doppelsubmit, Client-Retry).
- Ein Webhook wurde neu zugestellt und als neu behandelt.
- Ein Job lief zweimal (oder zwei Jobs liefen parallel).
- Ein Retry passierte, nachdem die E-Mail bereits dein System verlassen hatte.
- Zwei Codepfade senden dieselbe Vorlage (z. B. einmal im Controller und einmal im Model-Callback).
Letzteres kommt oft in schnell wachsenden Prototypen vor: Send-Logik wird in mehrere Handler kopiert und beide bleiben aktiv.
Fang mit einem Vorfall an und baue eine Timeline
Starte nicht mit dem Scannen des ganzen Codes. Fang mit einer echten E-Mail an, die ein Nutzer zweimal erhalten hat. Wähle eine einzelne Vorlage (z. B. „Passwort zurücksetzen“ oder „Quittung“) und ein enges Zeitfenster (5–15 Minuten), damit du nicht verschiedene Ereignisse vermischst.
Sammle jede Identifikation, die du zu diesem Vorfall finden kannst, damit du genau auf die Send-Versuche zeigen kannst, nicht nur auf den beschwerdeführenden Nutzer.
Für jede Kopie der E-Mail hole:
- Deine interne E-Mail-Record-ID (oder die DB-Row-ID)
- Die Message-ID/Response-ID des E-Mail-Providers
- Zeitstempel (erstellt, in Queue, gesendet, vom Provider akzeptiert)
- Die Business-Entity-IDs (user_id, order_id, invoice_id, reset_token_id)
- Jede Request-ID oder Job-ID, die mit dem Versand verbunden ist
Schreibe dann eine Klartext-Timeline vom Auslöser bis zur Provider-Akzeptanz. Logs helfen, aber es aufzuschreiben zwingt zur Klarheit.
Eine nützliche Timeline beantwortet vier Fragen: welches Ereignis passierte, welcher Codepfad hat es verarbeitet, welche Jobs wurden enqueued, und wie oft hat der Provider eine Nachricht akzeptiert.
Beispiel: Ein Nutzer klickt „Passwort zurücksetzen“ um 10:03:12. Deine API erstellt reset_token_id=7781 und enqueued einen Job um 10:03:13. Um 10:03:14 wiederholt der Client (oder ein Webhook wird neu zugestellt) und erstellt ein zweites Token und einen zweiten Job. Beide Jobs laufen und der Provider akzeptiert zwei Nachrichten um 10:03:20 und 10:03:22.
Instrumentiere den Send-Pfad, damit du Duplikate sehen kannst
Du kannst nicht beheben, was du nicht sehen kannst. Das erste Ziel ist klar: lass jeden Send-Versuch eine Spur hinterlassen, der du vom Auslöser bis zum Provider folgen kannst.
Finde zuerst jeden Ort, an dem deine App E-Mails senden kann. Viele Teams haben mehr als einen Pfad: einen Controller, der direkt sendet, einen Webhook-Handler, der „für den Fall“ sendet, und einen Hintergrundjob, der ebenfalls sendet. Füge eine klare Log-Zeile direkt vor dem Provider-Aufruf hinzu (der Moment, in dem du um den Versand bittest) und mache sie über alle Aufrufstellen hinweg konsistent.
Was du bei jedem Send-Versuch loggen solltest
Halte es langweilig und konsistent. Eine kleine Menge Felder ist besser als eine lange Nachricht, die niemand liest.
- Eine Korrelations-ID, die Request oder Job End-to-End verfolgt
- Trigger-Quelle (web_request, webhook, cron, background_job, manual_admin)
- Business-Event (password_reset, receipt, invite, email_change)
- Empfänger und Vorlagenname (oder Nachrichtentyp)
- Den Dedupe-Key, den du verwenden willst (auch wenn du ihn noch nicht durchsetzt)
Mit dem in place, wenn ein Nutzer sagt „Ich habe zwei E-Mails bekommen“, kannst du in den Logs nach Empfänger und Event suchen und dann nach Korrelations-ID und Dedupe-Key gruppieren. Duplikate zeigen sich oft als zwei verschiedene Trigger innerhalb von Sekunden.
Webhooks: Behandle Neuzustellungen als normal
Die meisten Webhook-Systeme retryen by design. Wenn dein Handler nicht idempotent ist, werden Retries zu doppelten E-Mails, selbst wenn alles „wie vorgesehen“ funktioniert. Die Lösung ist, davon auszugehen, dass jeder Webhook mehr als einmal geliefert werden kann.
Überprüfe zuerst, dass du Webhooks nicht bereits vor dem Erreichen deines Codes duplizierst. Es ist überraschend häufig, dass zwei Subscriptions auf denselben Endpoint zeigen (eine alte, die jemand vergessen hat, oder Staging, das auf Produktion zeigt). Die Payloads sehen gültig aus; der einzige Hinweis ist dasselbe Event, das zweimal auftaucht.
Verstehe dann, wann der Provider retryt. Viele senden bei Timeouts und 5xx-Fehlern erneut, manche sogar bei bestimmten 4xx-Antworten. Wenn dein Handler langsame Arbeit macht (E-Mail versenden, andere Services aufrufen, schwere Queries) bevor er antwortet, erhöhst du Timeouts und Retries.
Ein sichereres Muster ist: zuerst speichern, dann antworten, dann verarbeiten. Antworte 2xx erst nachdem die wichtigen Daten dauerhaft gespeichert sind (meistens in der Datenbank), sodass ein Retry das Ereignis bereits als vorhanden sehen kann.
Eine Checkliste mit hoher Signalstärke:
- Bestätige, dass pro Event-Typ und Umgebung nur eine aktive Subscription existiert.
- Logge die Webhook-Event-ID (vom Provider) zusammen mit deiner Request-ID.
- Speichere die Event-ID mit einer Unique-Constraint und einem Status (processed/unprocessed).
- Sende 2xx nachdem das Event gespeichert ist, nicht nachdem die E-Mail gesendet wurde.
- Wenn das Speichern fehlschlägt, gib einen Fehler zurück, damit der Retry nützlich ist statt schädlich.
Hintergrundjobs: Verhindere doppeltes Enqueue und doppeltes Ausführen
Hintergrundjobs sind eine häufige Quelle für Duplikate, weil die meisten Queues auf mindestens-einmal-Zustellung ausgelegt sind. Ein Job kann zweimal laufen und das System sieht das immer noch als akzeptabel an. Dein Code muss sicher sein, wenn derselbe Job nochmal auftaucht.
Ein Job kann aus normalen Gründen zweimal laufen: ein Worker crashed nach dem Senden, bevor er die Queue bestätigt, der Job timed out, oder ein Visibility-Timeout läuft ab und die Queue gibt dieselbe Payload an einen anderen Worker. Wenn der E-Mail-Versand dazwischenliegt, bekommt der Nutzer zwei Nachrichten.
Reduziere zuerst doppeltes Enqueue. Ein klassischer Bug ist Enqueue innerhalb einer Datenbank-Transaktion und anschließendem Rollback, oder Enqueue an zwei Stellen (API-Handler und Model-Callback). Bevorzuge Enqueue nach Commit, damit sich „Ereignis ist passiert“ und „E-Mail senden“-Job nicht auseinanderdriften.
Mache dann den Job sicher, zweimal ausgeführt zu werden. Der Worker sollte eine „haben wir das schon gesendet?“-Prüfung machen, bevor er den Provider aufruft.
Praktische Guards, die gut funktionieren:
- Verwende einen eindeutigen Job-Key, sodass die Queue Duplikate für dasselbe Business-Ereignis ablehnt.
- Schreibe eine „bereits enqueued“-Zeile, die nach Event keyed ist, und enqueu nur, wenn der Insert erfolgreich ist.
- Reserviere in der Worker-Logik atomar den Versand (oder erwirb ein Lock), bevor du sendest.
- Behalte Retries bei, aber begrenze sie, und logge, wenn ein Retry nach Provider-Akzeptanz passiert.
Wenn dein einziger Schutz „wir retryen bei Fehler“ ist, wirst du weiterhin Duplikate sehen, wenn der Fehler nach dem eigentlichen Versand passiert.
Füge Dedupe-Keys (Idempotenz) auf Business-Event-Ebene hinzu
Um Duplikate dauerhaft zu stoppen, führe keine Dedupe auf der Ebene des "Send API Calls" durch. Dedupe auf der Ebene des Business-Ereignisses: was in deiner App passiert ist und genau eine Nachricht verdient.
Beginne damit, zu definieren, was „dieselbe E-Mail“ für dein Produkt bedeutet. Eine praktische Definition ist meist: gleicher Empfänger, gleiches Business-Ereignis und gleiche Vorlage (oder E-Mail-Typ). „Passwort-Reset angefordert“ und „Passwort-Reset erfolgreich“ sind nicht dasselbe Ereignis, auch wenn sie im Posteingang ähnlich aussehen.
Ein Dedupe-Key sollte stabil und vorhersehbar sein, sodass jeder Codepfad denselben Wert berechnet:
password_reset_requested:{user_id}:{reset_token_id}order_receipt:{order_id}:{email_type}invite_sent:{workspace_id}:{invitee_email}
Das wichtigste Detail: speichere den Key, bevor du sendest.
Lege eine email_deliveries (oder ähnlich) Tabelle an mit einer Unique-Constraint auf dedupe_key. Wenn der Insert erfolgreich ist, besitzt du den Send. Wenn er konfligiert, hat es bereits jemand anders behandelt.
Bei Konflikt wähle ein Verhalten, das passt:
- Überspringe den Versand und logge „duplicate suppressed“.
- Aktualisiere ein Feld wie
last_attempt_at, wenn du Sichtbarkeit willst. - Gib dem Aufrufer Erfolg zurück und verwende den bestehenden Datensatz.
Entscheide auch das Dedupe-Fenster. Manche E-Mails sollten einmalig für immer sein (eine Quittung). Andere sollten Wiederholungen nach Zeit erlauben (eine tägliche Erinnerung). Für wiederholbare E-Mails baue Zeit in den Key ein (z. B. reminder:{user_id}:2026-01-20) oder verfalle alte Keys.
Ein realistisches Beispiel: zwei Passwort-Resets, ein Nutzer
Duplikate erscheinen in Tests oft harmlos, zeigen sich dann aber in Produktion, wenn Nutzer schnell klicken und Netzwerke unstabil sind.
Sara hat ihr Passwort vergessen. Sie öffnet die Reset-Seite und klickt „Reset-Link senden“. Die Seite wirkt langsam, also klickt sie nochmal.
Eine realistische Timeline, die zu zwei E-Mails führt:
- 10:02:11 Die erste Anfrage erstellt ein Reset-Token und enqueued
SendPasswordResetEmail. - 10:02:12 Sara klickt erneut. Eine zweite Anfrage enqueued denselben Job (oder triggert einen anderen Pfad, der ihn enqueued).
- 10:02:20 Der Job-Runner nimmt den ersten Job und ruft den E-Mail-Provider auf.
- 10:02:22 Der Provider-Aufruf timed out und dein Job retried.
- 10:02:23 Der zweite Job läuft ebenfalls. Jetzt hast du Overlap plus Retry.
In den Logs kann das so aussehen, als hätte die App „nur einmal gesendet“, während der Provider zwei akzeptierte Sends zeigt, oder einen akzeptierten Send plus einen Retry, der ebenfalls erfolgreich war.
Die Lösung ist, auf Business-Event-Ebene zu dedupen, nicht auf Job-ID-Ebene. Für Passwort-Reset ist ein stabiler Key user_id + reset_token (oder reset_token allein, wenn es eindeutig ist).
Wenn der Send-Code läuft, prüft er zuerst „haben wir für diesen Key bereits gesendet?“ Wenn ja, überspringt er den Provider-Aufruf und schreibt einen klaren Log-Eintrag wie „ignored duplicate attempt“, inklusive Dedupe-Key und Trigger-Quelle.
Das macht den zweiten Klick und den Retry zu sicheren No-Ops und behält trotzdem eine Audit-Spur für den nächsten Vorfall.
Häufige Fehler, die Duplikate zurückbringen
Duplikate überleben oft den ersten Fix, weil der Patch das Symptom behandelt, nicht den Auslöser. Alles sieht in Tests gut aus, und dann bringt der nächste Traffic-Spike oder Provider-Retry zwei (oder fünf) Nachrichten hervor.
Eine Falle ist, sich auf E-Mail-Provider-Suppressions-Tools zu verlassen und das Problem als gelöst zu betrachten. Suppression kann verbergen, was Nutzer sehen, aber deine App feuert weiterhin mehrere Send-Anfragen ab. Das macht das Debuggen auch schwerer, weil du weiterhin wiederholte „send attempted“-Einträge siehst.
Dedupe-Keys sind ein weiteres häufiges Problem. Wenn der Key zu breit ist (z. B. user_id + template), kannst du legitime Nachrichten blockieren (zwei separate Quittungen). Wenn der Key zu eng ist (z. B. eine zufällige UUID pro Anfrage), matcht er nie Duplikate, sodass Retries trotzdem erneut senden.
Race-Conditions sind der stille Killer. Wenn du den Dedupe-Eintrag nach dem Senden schreibst, können zwei Worker beide die „noch nicht gesendet“-Prüfung bestehen, beide senden und dann beide Erfolg schreiben. Reserviere den Key zuerst (ein atomarer Insert), dann sende.
Probleme, die Duplikate später wieder einführen:
- Ein Webhook bestätigt Erfolg, bevor der Event-State persistiert ist.
- Webhook-Neuzustellungen werden als Fehler betrachtet statt als normales Verhalten.
- Derselbe Job kann ohne Einzigkeits-Guard zweimal enqueued werden.
- Nur ein Trigger wird gefixt, aber ein zweiter Pfad (Admin-Aktion, Cron, Import) sendet weiterhin.
Kurze Checks bevor du den Fix ausrollst
Bevor du deployst, wähle einen E-Mail-Typ, der dupliziert wurde (Passwort-Reset, Quittung, Invite) und bestätige, dass du ihn End-to-End verfolgen kannst. Wenn du eine einzelne Nachricht nicht vom ersten Trigger bis zum Provider-Aufruf zurückverfolgen kannst, rätst du noch.
Eine praktische Regel: jede E-Mail sollte eine einzige Business-Event-Identität haben, und jedes System, das damit arbeitet, sollte Wiederholungen als normal behandeln.
Pre-Deploy-Checklist (schnell, hoher Signalgehalt)
Im Staging mit production-ähnlichen Retries an:
- Logs zeigen eine klare Kette: Trigger empfangen, Handler akzeptiert, Dedupe-Entscheidung, Job enqueued (falls vorhanden), Send versucht, Provider-Antwort aufgezeichnet.
- Webhook-Handler speichern die Provider-Event-ID (oder eine eigene) und ignorieren Neuzustellungen ohne Fehler zu werfen.
- Hintergrundjobs können retried werden ohne Seiteneffekte: läuft derselbe Job zweimal, beendet der Handler früh, statt zweimal zu senden.
- Ein eindeutiger Dedupe-Key wird in dauerhaften Speicher geschrieben, bevor der Send-Call gemacht wird, nicht danach.
- Du kannst Spitzen schnell sehen (auch ein einfaches Diagramm) für „E-Mails pro Minute“ und „Dedupe-Hits“.
Ein schneller „zerstör es absichtlich“-Test
Trigger dasselbe Ereignis zweimal (oder replay denselben Webhook-Payload). Erzwinge dann einen Fehler: kill den Worker mitten im Job, oder simuliere einen Timeout vom E-Mail-Provider.
Das erwartete Ergebnis ist langweilig: höchstens eine zugestellte E-Mail und Logs, die deutlich erklären, warum Duplikate blockiert wurden.
Nächste Schritte: mach es langweilig und halte es so
Nachdem Dedupe-Keys Duplikate in deinen Logs stoppen, rolle die Änderung wie jedes Produktionsupdate aus. Wenn du nervös bist, setze die Dedupe-Prüfung hinter ein Feature-Flag und schalte sie schrittweise ein. Fang mit einem E-Mail-Typ an (Passwort-Resets sind ein guter erster Kandidat) und erweitere, wenn sich die Metriken beruhigt haben.
Räume dann das Chaos auf, das Duplikate bereits erzeugt haben. Wenn du „E-Mail gesendet“-Records speicherst, willst du vielleicht Extras als Duplikate markieren, damit Support-Views und Reports nicht mehr falsch aussehen. Perfekte Historie ist weniger wichtig als zukünftige Zählungen, die dem entsprechen, was Nutzer tatsächlich erlebt haben.
Füge einen kleinen automatisierten Test hinzu, der beweist, dass der Handler idempotent ist: rufe dasselbe Ereignis zweimal mit demselben Dedupe-Key auf und assertiere, dass nur ein Send aufgezeichnet wird. Dieser einzelne Test verhindert oft, dass spätere Refactorings die Guard entfernen.
Ein paar Gewohnheiten halten es langfristig langweilig:
- Logge den Dedupe-Key bei jedem Send-Versuch und jedem Skip.
- Alertiere bei plötzlichen Spitzen an „als Duplikat übersprungen“ (das kann auf eine Trigger-Schleife hinweisen).
- Prüfe neue Webhook-Handler und Hintergrundjobs auf Idempotenz vor dem Merge.
- Halte den Dedupe-Speicher dauerhaft genug, um Neustarts und Retries zu überstehen.
Wenn du einen AI-generierten Codebestand geerbt hast, in dem E-Mail-Sends über copy-paste-Handler und Retries verstreut sind, kann ein fokussiertes Audit Tage des Rätselratens sparen. FixMyMess (fixmymess.ai) spezialisiert sich auf Diagnose und Reparatur AI-generierter Apps, einschließlich Hinzufügens von Business-Event-Idempotenz, sodass Webhooks und Job-Retries aufhören, doppelte E-Mails zu erzeugen.
Häufige Fragen
Was meinen Sie mit „doppelte E-Mails“ in der Produktion?
Behandle es als ein Business-Ereignis hat zwei Nachrichten erzeugt, nicht nur als „zwei SMTP-Aufrufe“. Benenne zuerst das Ereignis (z. B. password_reset_requested oder receipt_paid) und sorge dafür, dass jede Ebene Wiederholungen als normal und ungefährlich ansieht.
Was sind die häufigsten Gründe, dass Nutzer dieselbe E-Mail zweimal erhalten?
Meistens löst deine App den gleichen Versand zweimal aus: Doppelklicks oder Client-Retries, Webhook-Neuzustellungen, Hintergrundjob-Retries oder zwei verschiedene Codepfade, die dieselbe Vorlage senden. E-Mail-Anbieter senden in der Regel nur das, was du ihnen angefordert hast.
Wie debugge ich ein einzelnes Duplikat, ohne mich im gesamten Codebase zu verlieren?
Wähle einen echten Vorfall und baue eine Timeline. Sammle deine interne E-Mail-Record-ID, die Provider-Message-ID, Zeitstempel, Business-Entity-IDs (z. B. order_id oder reset_token_id) und die Request/Job-IDs, dann schreibe den genauen Pfad, der zu jedem Provider-Accept führte.
Was sollte ich loggen, damit Duplikate später leicht auffindbar sind?
Logge eine konsistente Zeile direkt vor jedem Provider-Aufruf mit einer Korrelations-ID, Trigger-Quelle, Business-Event-Name, Empfänger, Vorlage/Typ und dem Dedupe-Key (auch wenn du ihn noch nicht durchsetzt). Dann sieht man sofort, wenn zwei verschiedene Trigger innerhalb von Sekunden ausgelöst haben.
Wie verhindere ich, dass Webhook-Neuzustellungen doppelte E-Mails auslösen?
Geh davon aus, dass jeder Webhook mehr als einmal ankommen kann. Speichere die Webhook-Event-ID dauerhaft mit einer Unique-Constraint, antworte mit 2xx nachdem die Daten gespeichert sind, und verarbeite die Arbeit danach. So wird eine Neuzustellung zu einer harmlosen No-Op statt zu einem weiteren Versand.
Wie verhindere ich, dass Hintergrundjobs dieselbe E-Mail zweimal senden?
Da die meisten Queues mindestens-einmal-Zustellung bieten, kann ein Job wegen Timeouts, Abstürzen oder Sichtbarkeits-Timeouts zweimal laufen. Mach den Job idempotent: reserviere den Versand mit einem eindeutigen Dedupe-Eintrag, bevor du den Provider aufrufst, und beende früh, wenn er bereits reserviert/gesendet ist.
Was ist ein guter Dedupe- (Idempotency-)Key für E-Mail-Versendungen?
Erzeuge einen stabilen Schlüssel basierend auf dem Business-Ereignis, z. B. order_receipt:{order_id}:{email_type} oder password_reset_requested:{user_id}:{reset_token_id}. Speichere ihn vor dem Versand mit einer Unique-Constraint; wenn der Insert konfligiert, überspringe den Provider-Aufruf und logge „duplicate suppressed“.
Warum produziert „prüfen ob gesendet, dann senden“ trotzdem Duplikate?
Wenn du den „gesendet“-Eintrag erst nach dem Provider-Aufruf schreibst, können zwei Worker beide den „noch nicht gesendet“-Check passieren und beide senden. Die Standardlösung ist zuerst die atomare Reservierung (unique insert oder Lock), dann senden, dann als gesendet markieren.
Wie kann ich die Lösung testen, bevor ich in Produktion deploye?
Ein einfacher Fehlertest funktioniert: löse dasselbe Ereignis zweimal aus, spiele denselben Webhook-Payload erneut ab und erzwinge einen Fehler wie einen Worker-Absturz oder einen Provider-Timeout. Erwartetes Ergebnis: höchstens eine zugestellte E-Mail und Logs, die klar erklären, warum der zweite Versuch übersprungen wurde.
Kann FixMyMess helfen, wenn das in einer AI-generierten App passiert?
Wenn die Send-Logik über copy-paste in mehreren Handlern, Webhooks und Jobs verstreut ist, kommen Duplikate nach jedem Patch zurück. FixMyMess hilft, AI-generierte Codebasen zu diagnostizieren, Send-Pfade zu konsolidieren, Business-Event-Dedupe-Keys hinzuzufügen und Retries zu härten, damit Nutzer zuverlässig eine Nachricht erhalten.