Triggers vs Hintergrund‑Worker für zuverlässige Benachrichtigungen
Erfahre, wann Trigger oder Hintergrund‑Worker bei Benachrichtigungen sicherer sind – mit praktischen Hinweisen zu Wiederholungen, Transaktionen und dem Vermeiden von Duplikaten.

Warum die Zustellung von Benachrichtigungen in echten Apps scheitert
Benachrichtigungen klingen einfach: ein Nutzer macht etwas, dann geht eine E‑Mail oder SMS raus. Die meisten echten Fehler hängen jedoch mit Timing und Duplikaten zusammen. Nachrichten werden verschickt, bevor die Daten wirklich gespeichert sind, oder sie werden nach einem Teilfehler doppelt gesendet.
Eine „Benachrichtigung“ kann vieles sein: E‑Mail‑Belege, SMS‑Einmalcodes, Push‑Benachrichtigungen, In‑App‑Meldungen, Slack‑ oder Telegram‑Pings oder ein Webhook zu einem anderen System. Das gemeinsame Problem ist immer dasselbe: Sie versuchen, eine Datenbankänderung mit etwas außerhalb Ihrer App zu koordinieren.
Die Außenwelt ist unordentlich. Anbieter können langsam sein, Timeouts liefern oder eine Anfrage akzeptieren, während Ihre App nie die Erfolgsmeldung erhält. Ihre App kann während der Anfrage abstürzen oder neu starten. Selbst „erfolgreiche“ Sends können erneut ausgeführt werden wegen Infrastruktur‑Retries, Neustarts von Workern oder wenn ein Benutzer den Button noch einmal drückt.
Häufige Ursachen für fehlerhafte Zustellung sind Netzwerk‑Timeouts, Ausfälle oder Rate‑Limits des Providers, App‑Neustarts im ungünstigen Moment, Wiederholungen, die dieselbe Sendelogik ohne eindeutige Sperre erneut ausführen, und Entwürfe, bei denen ein Datenbank‑Schreibvorgang und ein externer Versand als ein einziger Schritt vermischt werden.
Wenn Leute von „zuverlässigen Benachrichtigungen“ sprechen, meinen sie meist eines von zwei Dingen:
- genau einmal zustellen, oder
- zumindest keine Duplikate liefern (Duplikate sind oft schlimmer als Verzögerungen).
Schnell und gleichzeitig absolut sicher zu sein ist schwer, daher muss man zwischen Geschwindigkeit, Sicherheit und Komplexität abwägen.
Deshalb ist die Entscheidung zwischen Triggern und Hintergrund‑Workern nicht nur eine Architektur‑Debatte. Es geht darum, wann ein Versand erlaubt ist, wie Fehler wiederholt werden und wie man verhindert, dass E‑Mails oder SMS bei Problemen doppelt ankommen.
Trigger und Hintergrund‑Worker: was sie bedeuten
Wenn Leute Trigger mit Hintergrund‑Workern vergleichen, vergleichen sie eigentlich, wo die Benachrichtigungslogik läuft und wie eng sie an die auslösende Aktion gebunden ist.
Ein Trigger heißt „tu es jetzt, wenn X passiert“. In vielen Apps bedeutet das, eine E‑Mail oder SMS direkt nach einer Nutzeraktion innerhalb derselben Web‑Anfrage zu senden. Trigger können auch auf Datenbank‑Ebene existieren: ein Datenbank‑Trigger läuft automatisch, wenn eine Zeile eingefügt oder aktualisiert wird. Beide Arten wirken unmittelbar, aber sie erben das Timing und die Grenzen dessen, was sie ausgelöst hat.
Ein Hintergrund‑Worker bedeutet „tu es bald, aber nicht im Vordergrund“. Es ist ein separater Prozess, der Jobs aus einer Warteschlange zieht und versucht, sie abzuarbeiten. Ihre Haupt‑App protokolliert, was passieren soll, und reagiert schnell, während der Worker die langsameren, fehleranfälligen Teile wie das Aufrufen eines E‑Mail‑ oder SMS‑Providers übernimmt.
Ein „Job" ist die Arbeitseinheit, die der Worker verarbeitet. Sie enthält typischerweise, wen man benachrichtigen soll, welche Vorlage, welche Daten einzufügen sind, den aktuellen Status (queued, processing, sent, failed), wie viele Versuche stattgefunden haben und manchmal eine geplante Zeit.
Ein typischer Benachrichtigungsablauf sieht so aus: Sie bereiten die Nachrichtendetails vor, legen einen Job in die Warteschlange, senden über einen Provider, protokollieren das Ergebnis und entscheiden dann, ob Sie wiederholen, abbrechen oder jemanden alarmieren.
Transaktionsgrenzen: wann es wirklich sicher ist zu senden
Eine Transaktionsgrenze ist die Linie zwischen „wir haben versucht zu speichern“ und „es ist wirklich gespeichert“. Bis die Datenbank committet, kann die Änderung noch zurückgerollt werden. Das ist wichtig, weil Benachrichtigungen schwer zurückzunehmen sind.
Wenn Sie eine E‑Mail oder SMS vor dem Commit senden, können Sie jemandem über etwas informieren, das nie stattgefunden hat. Ein Kunde könnte „Ihr Passwort wurde geändert“ oder „Ihre Bestellung ist bestätigt“ erhalten, und dann schlägt der Schreibvorgang wegen einer Constraint‑Fehler oder Timeout fehl. Jetzt ist der Nutzer verwirrt und der Support muss das aufklären.
Das Senden aus einem Datenbank‑Trigger wirkt verlockend, weil es automatisch bei Datenänderungen feuert. Der Haken ist, dass Trigger innerhalb derselben Transaktion laufen. Wenn die Transaktion zurückgerollt wird, haben Sie möglicherweise schon einen E‑Mail‑ oder SMS‑Provider aufgerufen.
Datenbank‑Trigger sind außerdem schwerer zu beobachten, zu testen und sicher zu wiederholen. Und wenn sie langsame externe Aufrufe machen, können sie Sperren länger offenhalten als erwartet und Datenbankprobleme schwerer diagnostizierbar machen.
Ein sichererer Ansatz ist die Outbox‑Idee: speichern Sie die Absicht zu benachrichtigen als Daten, committen Sie sie, und senden Sie danach.
Sie führen die Business‑Änderung aus und fügen in derselben Transaktion eine Outbox‑Zeile hinzu, die die Nachricht beschreibt (Empfänger, Kanal, Vorlage, Payload und einen eindeutigen Schlüssel). Nach dem Commit liest ein Hintergrund‑Worker die ausstehenden Outbox‑Zeilen, sendet die Nachricht und markiert sie als gesendet.
Sofortige Sends können für unverfängliche Informationsnachrichten in Ordnung sein, etwa „Wir bearbeiten Ihre Anfrage“. Für alles, das mit dem endgültigen Zustand übereinstimmen muss, warten Sie jedoch bis nach dem Commit.
Wiederholungen und Fehlerbehandlung: wo welche Lösung gewinnt
Wiederholungen sind meist der entscheidende Faktor.
Trigger: schnell, aber anfällig bei Fehlern
Die meisten triggerbasierten Designs haben keine gute Wiederholungsstrategie.
Wenn ein Trigger einen E‑Mail/SMS‑Provider aufruft und der Aufruf fehlschlägt, stehen meist zwei schlechte Optionen zur Auswahl:
- die Transaktion fehlschlagen lassen (und das ursprüngliche Update blockieren), oder
- den Fehler unterdrücken (und die Benachrichtigung stillschweigend verlieren).
Beides ist in Szenarien, in denen Zuverlässigkeit wichtig ist, nicht akzeptabel.
Versuche, in einem Trigger zu schleifen oder zu verzögern, verschlimmern die Lage oft, weil Transaktionen länger offen bleiben, Sperrzeiten steigen und die Datenbank langsamer wird. Und wenn die Datenbank oder App beim Senden abstürzt, können Sie oft nicht feststellen, ob der Provider die Anfrage erhalten hat.
Hintergrund‑Worker: für Wiederholungen gebaut
Ein Worker behandelt das Senden als eigene Aufgabe mit eigenem Zustand. Das macht es natürlich, nur dann zu wiederholen, wenn es Sinn macht.
Als praktische Regel wiederholen Sie temporäre Fehler (Timeouts, vorübergehende Netzwerkprobleme, Serverfehler, Rate‑Limits mit längerer Wartezeit). Permanente Probleme wiederholen Sie in der Regel nicht (ungültige Telefonnummern, fehlerhafte E‑Mails, harte Ablehnungen wie abgemeldete Nutzer). Bei „unbekannten“ Fehlern begrenzen Sie die Versuche und machen den Zustand sichtbar.
Backoff verhindert, dass Wiederholungen alles schlimmer machen. Beginnen Sie mit kurzer Wartezeit und verlängern Sie sie bei jedem Versuch (zum Beispiel 10s, 30s, 2m, 10m) und stoppen Sie nach einer festen Anzahl von Versuchen.
Speichern Sie den Wiederholungszustand mit jedem Job: Versuchszähler, nächste Versuchzeit, letzter Fehler (kurz und lesbar), letzte Versuchszzeit und einen klaren Status wie pending, sending, sent, failed.
Wenn Ihre App beim Senden neu startet, kann ein Worker festgefahrene Jobs erneut prüfen (z. B. status = sending mit altem Zeitstempel) und sie sicher wiederholen. Hier wird Idempotenz entscheidend, damit ein Retry nicht doppelt sendet.
Duplikate bei E‑Mails und SMS mit Idempotenz verhindern
Idempotenz bedeutet, dieselbe „Sende‑Benachrichtigung“-Aktion mehrmals ausführen zu können, ohne dass der Nutzer mehrmals die Nachricht erhält.
Das klassische Duplikat‑Szenario ist ein Timeout: Ihre App ruft einen Provider auf, die Anfrage time‑outet, und Ihr Code wiederholt. Die erste Anfrage könnte aber tatsächlich erfolgreich gewesen sein, sodass die Wiederholung ein Duplikat erzeugt.
Eine praktische Lösung ist, jeder Nachricht einen stabilen Schlüssel zu geben und diesen Schlüssel als einzige Quelle der Wahrheit zu behandeln. Gute Schlüssel beschreiben, was die Nachricht bedeutet, nicht wann Sie versucht haben zu senden.
Gängige Ansätze sind:
- eine generierte
notification_id, erstellt wenn Sie entscheiden „diese Nachricht soll existieren“, oder - ein geschäftlich abgeleitener Schlüssel wie
order_id + template + recipient(nur wenn das wirklich Einzigartigkeit definiert).
Speichern Sie dann ein Send‑Ledger (oft die Outbox‑Tabelle selbst) und lassen Sie alle Wiederholungen es vor dem Senden abfragen. Halten Sie Zustände einfach und sichtbar: created (entschieden), queued (bereit), sent (bestätigt), failed (bestätigter Fehler), canceled (nicht mehr nötig). Die wichtige Regel ist, dass Sie pro Idempotenz‑Schlüssel nur einen aktiven Datensatz zulassen.
Provider‑seitige Idempotenz kann helfen, wenn sie unterstützt wird, aber sie ersetzt nicht Ihr eigenes Ledger. Sie müssen weiterhin Ihre Wiederholungen, Deploys und Worker‑Restarts behandeln.
Behandeln Sie auch „unbekannte“ Ausgänge als erstklassig. Wenn eine Anfrage time‑outet, senden Sie nicht sofort erneut. Markieren Sie sie als ausstehend zur Bestätigung und wiederholen Sie sicher, indem Sie nach Möglichkeit den Provider‑Lieferstatus prüfen. Wenn Sie nicht bestätigen können, verzögern Sie und alarmieren, statt doppelt zu senden.
Ein sicheres Standardmuster: Outbox + Hintergrund‑Worker (Schritt für Schritt)
Wenn Sie ein sicheres Default wollen, ist das Outbox‑Muster plus Worker schwer zu schlagen. Es hält das Senden außerhalb Ihrer Business‑Transaktion, garantiert aber trotzdem, dass die Absicht zu benachrichtigen gespeichert ist.
Der Ablauf
Behandeln Sie „sende eine Benachrichtigung“ als Daten, die Sie speichern, nicht als Aktion, die Sie feuern.
Sie speichern die Business‑Änderung (zum Beispiel ein Bestellstatus‑Update) in den normalen Tabellen. In derselben Datenbank‑Transaktion fügen Sie auch eine Outbox‑Zeile mit Empfänger, Kanal (E‑Mail/SMS), Vorlage, Payload und einem Idempotenz‑Schlüssel hinzu. Sie committen die Transaktion. Erst nach diesem Punkt darf etwas gesendet werden.
Ein Hintergrund‑Worker holt regelmäßig ausstehende Outbox‑Zeilen, sendet sie und protokolliert das Ergebnis.
Fügen Sie einen einfachen Claim‑Schritt hinzu, damit nicht zwei Worker dieselbe Zeile greifen. Das kann ein Statuswechsel zu processing oder ein gesperrter Zeitstempel sein.
Duplikate blockieren und Fehler behandeln
Duplikate entstehen oft, wenn ein Send erfolgreich war, Ihre App aber abstürzt, bevor sie „sent“ protokolliert. Lösen Sie das, indem Sie das „mark sent“-Schreiben so gestalten, dass es wiederholbar sicher ist.
Verwenden Sie eine Uniqueness‑Regel (zum Beispiel eine Unique‑Constraint auf dem Idempotenz‑Schlüssel und Kanal). Wiederholen Sie mit klaren Regeln: begrenzte Versuche, zunehmende Verzögerungen, und nur für retrybare Fehler. Nach dem letzten Versuch verschieben Sie den Job in einen Dead‑Letter‑Status (z. B. failed_permanent), damit jemand ihn prüfen und manuell neu verarbeiten kann.
Das Monitoring kann einfach bleiben: Zählungen von pending, processing, sent, retrying und failed_permanent sowie der älteste Pending‑Zeitstempel.
Konkretes Beispiel: wenn eine Bestellung von „Packed“ zu „Shipped“ wechselt, aktualisieren Sie die Bestellzeile und erstellen eine Outbox‑Zeile mit Idempotenz‑Schlüssel order-4815-shipped. Selbst wenn der Worker beim Senden abstürzt, werden Wiederholungen nicht doppelt senden, weil das „sent“-Schreiben durch den eindeutigen Schlüssel geschützt ist.
Wann Hintergrund‑Worker die bessere Wahl sind
Datenbank‑Trigger reagieren im Moment der Datenänderung gut. Wenn die Aufgabe jedoch ist „sende eine Benachrichtigung zuverlässig unter den unordentlichen Bedingungen der echten Welt“, geben Hintergrund‑Worker Ihnen in der Regel mehr Kontrolle.
Worker passen besser, wenn Sie zeitgesteuerte Sends brauchen (Erinnerungen, Digests), hohe Volumen mit Rate‑Limits und Backpressure haben, Toleranz gegenüber Provider‑Variabilität (429‑Limits, langsame Antworten, kurze Ausfälle), mehrstufige Workflows (senden, auf Lieferung warten, dann nachfassen) oder plattformübergreifende Events, die abgeglichen werden müssen.
Ein einfaches Beispiel: Sie belasten einen Kunden, senden dann eine SMS‑Quittung und anschließend eine E‑Mail‑Rechnung. Wenn SMS wegen Gateway‑Problemen fehlschlägt, soll die Bestellung trotzdem als bezahlt gelten und später sicher erneut versucht werden können. Logik in einem Trigger mischt „Daten sind korrekt“ mit „ein Drittanbieter ist gerade verfügbar“, was riskant ist.
Hintergrund‑Worker machen auch die operative Kontrolle einfacher. Sie können eine Queue während eines Vorfalls pausieren, Fehler inspizieren und mit Verzögerung neu versuchen.
Häufige Fehler, die fehlende oder doppelte Nachrichten verursachen
Der schnellste Weg zu unzuverlässigen Benachrichtigungen ist „schick’s einfach“ überall dort, wo es praktisch scheint, und hoffen, dass Wiederholungen es richten. Ob Sie Trigger oder Worker nutzen — die Details zu Fehlern und Zuständen entscheiden, ob Nutzer eine, zwei oder gar keine Nachrichten bekommen.
Eine häufige Falle ist, aus einem Datenbank‑Trigger zu senden und anzunehmen, dass das nicht fehlschlagen kann. Trigger laufen in der Transaktion, also kann ein langsamer Provider‑Aufruf den Schreibvorgang verzögern, Timeouts erzeugen oder Tabellen länger sperren als erwartet. Schlimmer ist: wenn der Send fehlschlägt und Sie die Transaktion zurückrollen, könnten Sie später neu versuchen und doppelt senden, wenn der Provider den ersten Aufruf doch akzeptiert hat.
Wiederkehrende Fehler:
- Alles auf die gleiche Weise wiederholen, inklusive permanenter Fehler (schlechte E‑Mail, blockierte Nummer).
- Nicht zwischen „queued“ und „sent“ trennen, sodass Sie nach einem Crash nicht wissen, was sicher wiederholbar ist.
- Zeitstempel als Dedupe‑Schlüssel verwenden, wodurch Wiederholungen die Einzigartigkeit umgehen.
- Provider‑Aufrufe im Nutzer‑Request‑Pfad machen (Checkout und Formular‑Submit sollten nicht auf Gateways warten).
- Provider‑Timeouts als „nicht zugestellt“ behandeln, obwohl viele tatsächlich „unbekannt“ sind.
Ein einfaches Beispiel: Sie senden eine SMS, der Provider time‑outet, und Sie wiederholen. Wenn die erste Anfrage tatsächlich erfolgreich war, bekommt der Nutzer zwei Codes. Die Lösung ist, einen stabilen Idempotenz‑Schlüssel (wie notification_id) zu protokollieren, die Nachricht vor dem Senden als queued zu markieren und erst nach einer klaren Erfolgsantwort als sent einzutragen.
Schnelle Checks bevor Sie Benachrichtigungen ausliefern
Die meisten Bugs bei Benachrichtigungen betreffen nicht das Werkzeug, sondern Timing, Wiederholungen und fehlende Aufzeichnungen.
Stellen Sie sicher, dass Sie erst nach dem sicheren Commit der Datenbank schreiben. Wenn Sie innerhalb derselben Transaktion senden und diese später zurückgerollt wird, bekommen Nutzer womöglich eine Nachricht über etwas, das nie passiert ist.
Geben Sie jeder Benachrichtigung außerdem eine eindeutige Identität. Vergeben Sie einen stabilen Idempotenz‑Schlüssel (z. B. order_id + event_type + channel) und erzwingen Sie ihn im Speicher, sodass ein Retry nicht eine zweite „neue“ Benachrichtigung erzeugt.
Vor dem Release prüfen Sie diese Grundlagen:
- Senden erfolgt nach dem Commit, nicht während des Schreibens.
- Jede Benachrichtigung hat einen einzigartigen Idempotenz‑Schlüssel und Duplikate werden abgewiesen.
- Wiederholungen sind sicher: das System kann denselben Job erneut ausführen und höchstens einmal senden.
- Jeder Versuch wird protokolliert (Status, last_error, Zeitstempel).
- Versuche sind begrenzt und feststeckende Elemente haben einen klaren Ort zur Überprüfung und Neubearbeitung.
Testen Sie das Neustartverhalten absichtlich. Beenden Sie den Worker mitten im Senden, starten Sie ihn neu und verifizieren Sie, dass nichts doppelt gesendet wird. Machen Sie das gleiche unter Datenbanklast.
Ein einfaches Validierungsszenario: Ein Nutzer ändert seine Telefonnummer und Sie senden eine SMS‑Verifizierung. Wenn der SMS‑Provider time‑outet, wiederholt Ihre App. Mit einem guten Idempotenz‑Schlüssel und Versuchprotokoll senden Sie entweder einmal oder versuchen später sicher erneut, ohne zu spammen.
Beispiel: Bestell‑Updates ohne Doppelversand
Ein Shop sendet zwei Arten von Nachrichten: (1) eine Bestellbestätigung per E‑Mail direkt nach der Zahlung und (2) SMS‑Updates, wenn das Paket „Out for delivery“ und „Delivered“ ist.
Das kann schiefgehen, wenn Sie zu früh senden (z. B. in einem Datenbank‑Trigger): der Zahlungs‑Schritt schreibt eine orders‑Zeile, der Trigger feuert und mailt den Kunden, und dann schlägt die Zahlungsfreigabe kurz darauf fehl. Jetzt haben Sie eine „Danke für Ihre Bestellung“‑Mail für eine Bestellung, die nie echt wurde.
Umgekehrt: der Lieferstatus wechselt zu „Out for delivery“, Sie rufen den SMS‑Provider auf und der Provider time‑outet. Sie wissen nicht, ob die Nachricht gesendet wurde. Wenn Sie sofort erneut senden, riskieren Sie zwei SMS; wenn Sie nicht wiederholen, riskieren Sie, gar keine SMS zu senden.
Ein sicherer Ablauf nutzt eine Outbox‑Zeile plus Hintergrund‑Worker. Die App committet die Bestellung oder Statusänderung und schreibt in derselben Transaktion eine Outbox‑Zeile wie „send template X to user Y, channel SMS, idempotency key Z“. Erst nach dem Commit liefert ein Worker die Nachrichten aus.
Ein einfacher Zeitstrahl:
- Zahlung gelingt, Transaktion committet, Outbox‑Zeile für die Bestätigungs‑E‑Mail wird gespeichert.
- Worker sendet die E‑Mail und markiert die Outbox mit einer Provider‑Message‑ID als gesendet.
- Lieferstatus ändert sich, Transaktion committet, Outbox‑Zeile für das SMS‑Update wird gespeichert.
- Provider time‑outet, Worker markiert die Outbox als retryable und versucht es später erneut mit demselben Idempotenz‑Schlüssel.
Bei einem Retry ist die Outbox‑Zeile die einzige Quelle der Wahrheit. Sie erzeugen keine zweite „Send“-Anfrage, sondern schließen die erste ab.
Für den Support ist das auch klarer. Er sieht Nachrichten in failed mit dem letzten Fehler (Timeout, schlechte Nummer, blockierte E‑Mail), wie viele Versuche gemacht wurden und ob ein Neusenden ohne Doppelversand sicher ist.
Nächste Schritte: wählen Sie ein Muster und implementieren Sie es sauber
Wählen Sie ein Default und schreiben Sie es nieder. Inkonsistentes Verhalten kommt meist von zufälliger Vermischung von Triggern und Workern.
Beginnen Sie klein mit einer Outbox‑Tabelle und einer Worker‑Schleife. Das erste Ziel ist nicht Geschwindigkeit, sondern Korrektheit: speichern Sie, was Sie senden wollen, senden Sie es nach dem Commit und markieren Sie es nur als gesendet, wenn der Provider bestätigt.
Ein einfacher Rollout‑Plan:
- Definieren Sie Events (order_paid, ticket_assigned) und welche Kanäle sie nutzen dürfen.
- Fügen Sie eine Outbox‑Tabelle mit event_id, recipient, payload, status, attempts, next_retry_at, sent_at hinzu.
- Bauen Sie einen Worker, der pending Zeilen abfragt, sendet und den Status zentral aktualisiert.
- Fügen Sie Idempotenz mit einem einzigartigen Schlüssel pro Nachricht hinzu und „do nothing if already sent".
- Teilen Sie Fehler in retrybar (Timeouts, 5xx) vs. nicht retrybar (schlechte Nummer, blockierte E‑Mail).
Bevor Sie das Volumen skalieren, fügen Sie grundlegende Sichtbarkeit hinzu. Tracken Sie die Anzahl der Pending‑Jobs, Fehlerquote und das Alter der ältesten Pending‑Nachricht. Wenn die älteste Pending‑Nachricht kontinuierlich wächst, haben Sie wahrscheinlich einen feststeckenden Worker, einen Provider‑Ausfall oder einen Logikfehler.
Wenn Sie in AppMaster (appmaster.io) bauen, lässt sich dieses Muster gut abbilden: modellieren Sie die Outbox im Data Designer, schreiben Sie Business‑Änderung und Outbox‑Zeile in einer Transaktion und laufen Sie die Sende‑und‑Wiederholungs‑Logik in einem separaten Hintergrundprozess. Diese Trennung hält die Zustellung zuverlässig, selbst wenn Provider oder Deploys Probleme machen.
FAQ
Hintergrund‑Worker sind in der Regel die sicherere Standardwahl, weil Senden langsam und fehleranfällig ist und Worker für Wiederholungen und Sichtbarkeit gebaut sind. Trigger können schnell sein, sind aber eng an die Transaktion oder den Request gebunden, der sie ausgelöst hat, wodurch Fehler und Duplikate schwerer sauber zu handhaben sind.
Weil der Datenbank‑Schreibvorgang noch zurückgerollt werden kann. Dann können Sie Nutzer über eine Bestellung, Passwortänderung oder Zahlung informieren, die nie wirklich bestätigt wurde — und eine E‑Mail oder SMS lässt sich nach dem Versand nicht zurückholen.
Ein Datenbank‑Trigger läuft in derselben Transaktion wie die Zeilenänderung. Ruft er einen E‑Mail‑/SMS‑Provider auf und die Transaktion schlägt später fehl, haben Sie eventuell eine echte Nachricht über eine nicht persistente Änderung verschickt oder die Transaktion durch einen langsamen externen Aufruf blockiert.
Das Outbox‑Muster speichert die Absicht zu senden als Datensatz in Ihrer Datenbank, in derselben Transaktion wie die Business‑Änderung. Nach dem Commit liest ein Worker die ausstehenden Outbox‑Zeilen, sendet die Nachricht und markiert sie als gesendet. So werden Timing und Wiederholungen deutlich sicherer.
Oft ist das Ergebnis bei einem Timeout eher „unbekannt“ als „fehlgeschlagen“. Ein gutes System protokolliert den Versuch, verschiebt und wiederholt sicher mit derselben Nachrichten‑Identität, statt sofort erneut zu senden und ein Duplikat zu riskieren.
Nutzen Sie Idempotenz: geben Sie jeder Benachrichtigung einen stabilen Schlüssel, der beschreibt, was die Nachricht bedeutet (nicht wann Sie versucht haben zu senden). Speichern Sie diesen Schlüssel in einem Ledger (oft die Outbox‑Tabelle) und erzwingen Sie genau einen aktiven Datensatz pro Schlüssel, sodass Wiederholungen dieselbe Nachricht abschließen statt eine neue zu erzeugen.
Wiederholen Sie temporäre Fehler wie Timeouts, 5xx‑Antworten oder Rate‑Limits (mit Wartezeit). Wiederholen Sie nicht permanente Fehler wie ungültige Adressen, gesperrte Nummern oder harte Bounces; markieren Sie diese als fehlgeschlagen und machen Sie sie sichtbar, damit Daten korrigiert werden können statt das System zuzuballern.
Ein Background‑Worker kann nach Jobs suchen, die zu lange im Status sending stecken, sie wieder auf retrybar setzen und mit Backoff erneut versuchen. Das funktioniert nur sicher, wenn jeder Job seinen Zustand protokolliert (Versuche, Zeitstempel, letzter Fehler) und Idempotenz Doppel‑Sends verhindert.
Sie müssen beantworten können „Ist es sicher, neu zu versuchen?“ Speichern Sie klare Status wie pending, processing, sent und failed, plus Versuchszähler und letzten Fehler. Das macht Support und Debugging praktikabel und erlaubt der Anwendung, ohne Raten zu raten wiederherzustellen.
Modellieren Sie eine Outbox‑Tabelle im Data Designer, schreiben Sie das Business‑Update und die Outbox‑Zeile in eine Transaktion, und führen Sie die Sende‑und‑Wiederholungs‑Logik in einem separaten Hintergrundprozess aus. Verwenden Sie pro Nachricht einen Idempotenz‑Schlüssel und protokollieren Sie Versuche, damit Deploys, Wiederholungen und Worker‑Restarts keine Duplikate erzeugen.


