Benachrichtigungen beginnen als eine Zeile Code: Wenn eine Bestellung verschickt wird, sende eine E-Mail. Ein Jahr später verschickt dieselbe Firma Push, E-Mail und SMS aus einem Dutzend Services, Nutzer bekommen dieselbe Meldung dreimal, niemand kann sagen, ob eine E-Mail zum Zurücksetzen des Passworts angekommen ist, und Marketingnachrichten wecken Leute um drei Uhr nachts.
Ein Benachrichtigungssystem behebt das, indem es einen Service zwischen den Code stellt, der entscheidet, dass etwas passiert ist, und die Provider, die Nachrichten zustellen. Dieser Beitrag entwirft ihn: die API, die Prüfungen vor jedem Versand, die Zustellung pro Kanal mit Retries und das Tracking. Das Diagramm gegen Ende zeigt den gesamten Ablauf.
Anforderungen
- Produktservices senden eine Benachrichtigung mit einem Aufruf, ohne zu wissen, welche Kanäle der Nutzer aktiviert hat.
- Push (iOS und Android), E-Mail und SMS, mit Templates pro Sprache.
- Nutzer steuern Kanäle und Themen, und Ruhezeiten werden eingehalten.
- Dasselbe Event erzeugt nie zweimal dieselbe Nachricht.
- Jede Nachricht hat einen Status: gesendet, zugestellt, zurückgewiesen, fehlgeschlagen.
- Ein langsamer Provider verzögert nie die anderen Kanäle.
Die API: Events statt Nachrichten
Produktservices sollten keine E-Mails bauen. Sie senden ein Event mit den Fakten und einem Idempotency-Key:
POST /notifications
{
"idempotencyKey": "order-8812-shipped",
"userId": "u_7",
"type": "order_shipped",
"data": {"orderId": "8812", "carrier": "UPS", "eta": "2026-10-30"}
}
Alles andere gehört dem Benachrichtigungsservice: welche Kanäle dieser Typ nutzt, welches Template, welche Sprache und ob der Nutzer das überhaupt will. Wenn ein Team den Text ändern oder einem Benachrichtigungstyp SMS hinzufügen will, ändert sich im Bestellservice nichts.
Bevor irgendetwas versendet wird
In der API laufen in dieser Reihenfolge vier Prüfungen, bevor Arbeit einen Kanal erreicht:
- Deduplizieren. Speichere den Idempotency-Key mit einer TTL und verwirf Wiederholungen. Retries in vorgelagerten Services sind normal und sollten Nutzer nicht als Duplikate erreichen.
- Präferenzen prüfen. Schlag die Kanäle des Nutzers, Abmeldungen pro Thema und Ruhezeiten nach. Sicherheitsmeldungen wie Passwort-Resets umgehen meist Marketing-Abmeldungen; Marketing tut das nie. Schreib diese Regel auf.
- Volumen begrenzen. Begrenze Nachrichten pro Nutzer, Kanal und Stunde. Ein Bug, der 500 Events auslöst, sollte nicht zu 500 Push-Benachrichtigungen werden.
- Rendern. Wähle das Template für Benachrichtigungstyp, Kanal und Sprache des Nutzers und fülle es mit den Event-Daten.
Ruhezeiten brauchen die Zeitzone des Nutzers. Halte die Nachricht zurück und plane sie für das Ende des Zeitfensters ein, statt sie zu verwerfen, außer sie ist zeitkritisch.
Eine Queue und ein Worker-Pool pro Kanal
Nach den Prüfungen schreibt die API pro Kanal einen Job in die Queue dieses Kanals. Getrennte Queues sind die wichtigste Entscheidung im Design: Wenn der SMS-Provider langsamer wird, stauen sich SMS-Jobs in ihrer eigenen Queue, während Push und E-Mail weiterfließen.
Kanal-Worker rufen die Provider auf und kümmern sich um deren Eigenheiten:
- Push. iOS-Geräte laufen über den Apple Push Notification Service und Android über die FCM HTTP v1 API. Gerätetokens veralten: APNs antwortet mit 410 auf ein Token, das nicht mehr aktiv ist, und FCM liefert einen Unregistered-Fehler. Lösche diese Tokens, sonst zahlst du weiter Latenz für Nachrichten, die niemand bekommt.
- E-Mail. Versende über einen Provider und melde Bounces und Beschwerden zurück. Wenn du Massen-E-Mails verschickst, füge One-Click-Unsubscribe-Header nach RFC 8058 hinzu. Die Absenderrichtlinien von Gmail verlangen One-Click-Unsubscribe für Marketing- und Abo-Nachrichten von Absendern mit mehr als 5.000 Nachrichten pro Tag.
- SMS. Der teuerste Kanal pro Nachricht und der am stärksten regulierte. Nutze ihn für Codes und dringende Meldungen und prüfe die Regeln jedes Landes, in das du sendest (dieser Beitrag ist keine Rechtsberatung).
Worker wiederholen vorübergehende Fehler mit exponentiellem Backoff und Jitter und verschieben einen Job nach einer festen Zahl an Versuchen in eine Dead-Letter-Queue. Dauerhafte Fehler wie eine ungültige Adresse werden nicht wiederholt.
Zustellung verfolgen
Provider melden per Webhook oder Antwortcode, was passiert ist: angenommen, zugestellt, zurückgewiesen, geöffnet. Schreib jede Statusänderung in ein Zustell-Log mit der Benachrichtigungs-ID als Schlüssel. Der Support kann dann in Sekunden beantworten, ob der Nutzer die Reset-E-Mail bekommen hat, und du kannst Zustellraten pro Kanal und Provider messen.
Statusmeldungen kommen in falscher Reihenfolge und manchmal doppelt an. Speichere sie als Events mit Zeitstempel und leite daraus den aktuellen Zustand ab, statt eine Statusspalte zu überschreiben.
In-App-Benachrichtigungen
Viele Produkte führen zusätzlich einen In-App-Posteingang: die Glocke mit Zähler. Behandle ihn als vierten Kanal, der in eine Datenbank schreibt, statt einen Provider aufzurufen. Speichere eine Zeile pro Nutzer und Benachrichtigung mit einem Gelesen-Flag, indexiere nach Nutzer und Zeit und lass die App die neueste Seite und einen Ungelesen-Zähler abrufen. Markiere Einträge gebündelt als gelesen, damit ein Nutzer, der den Posteingang öffnet, keine fünfzig Schreibvorgänge auslöst.
Überschlagsrechnung
Bei einer Million täglich aktiver Nutzer und fünf Benachrichtigungen pro Nutzer und Tag verschickt das System 5 Millionen Benachrichtigungen am Tag, im Schnitt etwa 58 pro Sekunde. Entscheidend sind die Spitzen: Eine Kampagne oder ein Incident-Alarm kann in wenigen Minuten eine Million Nachrichten verschicken. Dimensioniere die Queues für den Burst, lass die Worker sie in dem Tempo leeren, das jeder Provider erlaubt, und rechne damit, dass Provider dich drosseln, bevor deinen eigenen Workern die Kapazität ausgeht.
Das Architekturdiagramm
Diagramminhalt
- Product services
- Notification API
- Preferences
- Push queue
- Email queue
- SMS queue
- Push workers
- Email workers
- SMS workers
- APNs and FCM
- Email provider
- SMS provider
- Delivery log
- event
- opt-outs, quiet hours
- status
- webhooks
So liest du es:
- Links: Produktservices senden Events mit Idempotency-Key an die Benachrichtigungs-API. Die API liest den Präferenzspeicher und führt die Prüfungen aus, die im Markdown-Block stehen.
- Mitte: Jeder Kanal hat eine eigene Queue und einen eigenen Worker-Pool, also staut ein langsamer Provider nur seinen eigenen Kanal.
- Rechts: Worker rufen APNs und FCM, den E-Mail-Provider und den SMS-Provider auf. Statusmeldungen fließen als gepunktete Kanten zurück ins Zustell-Log, weil sie später und asynchron ankommen.
Was ins Diagramm gehört
- Die Prüfungen und ihre Reihenfolge. Deduplizierung, Präferenzen, Limits und Rendering sind die eigentliche Logik des Systems.
- Eine Spur pro Kanal. Wenn sich in deinem Diagramm alle Kanäle eine Queue teilen, tun sie das in Produktion vermutlich auch.
- Retry- und Dead-Letter-Verhalten. Notiere Backoff und Versuchslimit an jedem Worker.
- Den Rückweg der Status. Ohne ihn kann niemand eine fehlende Nachricht debuggen.
Mehr zur Queue- und Consumer-Seite findest du im Beitrag zur ereignisgesteuerten Architektur. Das Microservices-Architekturdiagramm zeigt, wo ein gemeinsamer Service wie dieser zwischen den anderen sitzt, und der Beitrag zum DSGVO-Data-Mapping lohnt sich, bevor du Kontaktdaten und Zustell-Logs speicherst.
In Archboard nachbauen
- Melde dich mit Google oder GitHub an und leg ein Diagramm an.
- Füge aus dem C4-Modell-Pack einen Container für die Produktservices und einen weiteren für die Benachrichtigungs-API hinzu, darunter eine Datenbank für die Präferenzen.
- Füge aus dem Daten-Pack drei Warteschlange-Nodes hinzu, einen pro Kanal, und je einen Container für die Worker jedes Kanals.
- Füge aus dem C4-Modell-Pack drei Externes System-Nodes für die Provider hinzu und eine Datenbank für das Zustell-Log.
- Zeichne die Status-Kanten gepunktet und füge einen Markdown-Block aus dem Basis-Pack mit den Prüfungen vor dem Versand hinzu.
Das Beispiel nutzt 14 Diagramm-Nodes und bleibt damit im Free-Plan.
FAQ
Warum nicht jeden Service seine eigenen Benachrichtigungen senden lassen? Weil Präferenzen, Ruhezeiten, Deduplizierung und Zustell-Tracking dann in jedem Service gebaut werden müssen und auseinanderdriften. Ein Benachrichtigungsservice wendet sie überall gleich an.
Wie verhindere ich doppelte Benachrichtigungen? Verlange bei jeder Anfrage einen Idempotency-Key und speichere ihn eine Weile. Ein wiederholter Key wird bestätigt und verworfen.
Sollten Push, E-Mail und SMS eine Queue teilen? Nein. Getrennte Queues und Worker-Pools verhindern, dass ein langsamer oder fehlerhafter Provider die anderen Kanäle verzögert.
Wie gehe ich mit Ruhezeiten um? Speichere Zeitzone und Ruhefenster jedes Nutzers und plane nicht dringende Nachrichten für das Ende des Fensters ein, statt sie zu senden oder zu verwerfen.
Woher weiß ich, dass eine Benachrichtigung zugestellt wurde? Erfasse Provider-Antworten und Webhooks in einem Zustell-Log mit der Benachrichtigungs-ID als Schlüssel. Zustellung bedeutet je Kanal etwas anderes, also speichere die Statusnamen des Providers.