ADR-024: Verarbeitung von Rücklaufmeldungen (Bounces)¶
Stand: 14.10.2026 09:17 Branch: main Status: Akzeptiert
Kontext¶
DiveLogix360 versendet Plattformnachrichten gemäss ADR-016 über Hostpoint mit noreply@divelogix360.ch als Absender und bounces@divelogix360.ch als Rücklaufadresse. Hostpoint bietet keine Bounce-Webhooks. Die Prüfungen vom 12.10.2026 (Nachweis Hostpoint-Mailversand, Abschnitt 6) zeigen zwei Formen der Unzustellbarkeit:
- Synchrone Ablehnung: Hostpoint lehnt die Empfängeradresse bereits beim Versand ab, zum Beispiel mit
550 unroutable address. Der MailService erhält den Fehler direkt. - Asynchrone Rücklaufmeldung: Hostpoint nimmt die Nachricht an, der Empfängerserver lehnt sie danach ab. Hostpoint sendet eine Meldung nach RFC 3464 (
multipart/report; report-type=delivery-status) anbounces@divelogix360.ch. Die Meldung enthält die Message-ID der Originalnachricht im KopfReferences:sowie die vollständige Originalnachricht.
Unzustellbare Einladungen müssen für die einladende Person erkennbar sein. Ein rein manueller Ablauf ist bei mehreren Tenants nicht zuverlässig, und der Abnahmetest UC00-09-32 verlangt einen prüfbaren Bounce-Prozess.
Geprüfte Varianten¶
Variante A: Nur betrieblicher Ablauf¶
- Eine zuständige Person prüft das Rücklaufpostfach regelmässig und setzt den Versandstatus von Hand.
- Kein zusätzlicher Code und keine neuen Abhängigkeiten.
- Fehleranfällig, nicht automatisiert testbar und mit wachsender Tenant-Zahl nicht tragfähig.
Variante B: Betrieblicher Ablauf und automatischer IMAP-Abruf¶
- Ein Dienst im Backend ruft das Rücklaufpostfach regelmässig ab, wertet standardisierte Meldungen aus und ordnet sie dem Versandprotokoll zu.
- Der betriebliche Ablauf bleibt für nicht automatisch verarbeitbare Meldungen bestehen.
- Benötigt zwei zusätzliche Bibliotheken und einen IMAP-Zugang zum Rücklaufpostfach.
Variante C: Zuordnung über variable Rücklaufadressen (VERP)¶
- Jede Nachricht erhält eine eigene Rücklaufadresse, zum Beispiel
bounces+<id>@divelogix360.ch. - Ob Hostpoint Unteradressen unterstützt und ob die strikte Absenderrichtlinie sie zulässt, ist nicht belegt.
- Die Zuordnung über die Message-ID ist ohne diese Annahme möglich.
Variante D: Wechsel zu einem Anbieter mit Bounce-Webhooks¶
- Widerspricht der Providerwahl nach ADR-016 und erfordert eine neue Providerbewertung.
Entscheidung¶
Variante B wird umgesetzt.
Betrieblicher Ablauf¶
- Das Runbook Bounce-Postfach regelt Zuständigkeit, Prüfintervall und Vorgehen für Meldungen, die nicht automatisch verarbeitet werden können.
- Verarbeitete und manuell bearbeitete Rücklaufmeldungen werden unverzüglich gelöscht, weil sie die vollständige Originalnachricht einschliesslich Einladungslink enthalten.
Automatische Verarbeitung¶
- Ein Dienst im Backend ruft
bounces@divelogix360.chper IMAP über TLS (Port 993, Zertifikatsprüfung, mindestens TLS 1.2) in einem einstellbaren Intervall ab; Standard sind 5 Minuten. Posteingang und Spam-Ordner werden geprüft. - Die Verarbeitung ist wiederholbar ohne Schaden (idempotent): Eine doppelt verarbeitete Meldung setzt denselben Status erneut, und das Löschen einer bereits gelöschten Meldung bleibt ohne Wirkung. Eine Sperre im Prozess verhindert überlappende Läufe derselben Instanz. In V1 läuft eine Backend-Instanz. Eine Datenbanksperre wird nicht verwendet, weil der Verbindungs-Pooler keine dauerhafte Sitzung für eine sitzungsweite Sperre zusichert und transaktionsgebundene Sperren auf die Transaktionsdauer von 15 Sekunden begrenzt wären.
- Verarbeitet werden nur Meldungen mit
multipart/report; report-type=delivery-status. Als endgültig unzustellbar giltAction: failedmit Status5.x.x. Vorübergehende Fehler (4.x.x) und Verzögerungsmeldungen ändern keinen Status und werden gelöscht. - Die Zuordnung erfolgt über die Message-ID der Originalnachricht aus
References:, ersatzweise aus der angehängten Originalnachricht, gegennotification_log.provider_message_id. - Bei einem Treffer werden
status = bounced,failed_atunderror_code = DSN_<Code>gesetzt. Der Code stammt ausDiagnostic-Code, ersatzweise ausStatus, weil Hostpoint inStatusteilweise nur den allgemeinen Code5.0.0meldet. - Synchrone Ablehnungen werden einheitlich als
error_code = SMTP_<Antwortcode>protokolliert. Ein 5xx-Code gilt ebenfalls als endgültig unzustellbar. - Empfängeradressen, Diagnosetexte und Nachrichteninhalte werden nicht gespeichert.
- Meldungen ohne Treffer oder ohne Standardformat werden in den Ordner «Manuell-pruefen» verschoben und als Warnung ohne personenbezogene Daten im Systemprotokoll vermerkt.
- Die Verarbeitung läuft im Plattformkontext (
dlx_app_platform). Für die Tenant-Rolle bleibtnotification_logunverändert nur anhängbar.
Zuordnung zu Einladungen¶
notification_logerhält ein optionales Feldinvitation_token_id, das auf die versendete Einladung verweist. Die Verknüpfung ist wie die übrigen Referenzen tenantgebunden.- Der Zustellstatus einer Einladung wird aus ihrem letzten Versandeintrag abgeleitet. Eine Einladung gilt als unzustellbar, wenn dieser Eintrag
bouncedist oderfailedmit einem endgültigen Fehlercode. - Der Lebenszyklus der Einladung (
pending,used,revoked,expired) bleibt unverändert. Ein zusätzlicher Einladungsstatus wird nicht eingeführt. - Die einladende Person erkennt den Zustellstatus über die Programmierschnittstelle; die Anzeige in der Benutzeroberfläche folgt mit dem Frontend.
Umgebungen¶
- Der Abruf ist über
BOUNCE_POLL_ENABLEDschaltbar und in der Entwicklung standardmässig deaktiviert. - Ein Rücklaufpostfach wird immer nur von einer Umgebung abgerufen, weil eine andere Umgebung die Meldungen nicht zuordnen könnte und sie verschieben würde. Vor dem Produktivbetrieb wird geprüft, ob die Entwicklung ein eigenes Rücklaufpostfach erhält.
- Zugangsdaten (
BOUNCE_IMAP_HOST,BOUNCE_IMAP_PORT,BOUNCE_IMAP_USER,BOUNCE_IMAP_PASSWORD) werden ausschliesslich in der nicht versionierten Umgebungskonfiguration beziehungsweise der produktiven Secret-Verwaltung geführt.
Neue Abhängigkeiten¶
| Bibliothek | Zweck |
|---|---|
imapflow |
IMAP-Zugriff mit TLS und Ordnerverwaltung |
mailparser |
Zerlegung der MIME-Struktur von Rücklaufmeldungen |
Die Zeitsteuerung des Abrufs erfolgt mit Node.js-Bordmitteln (setInterval mit sauberem Start und Stopp im Dienst). Auf @nestjs/schedule wird verzichtet, um eine zusätzliche, an die NestJS-Version gebundene Abhängigkeit zu vermeiden. |
Konsequenzen¶
- ADR-016 wird angepasst: Rücklaufmeldungen werden nicht mehr nur betrieblich überwacht, sondern gemäss dieser Entscheidung verarbeitet.
- Eine fünfte Migration ergänzt
notification_log.invitation_token_idmit tenantgebundenem Fremdschlüssel und erweitert die Prüfregel für tenantlose Einträge. Prisma-Schema, Datenkatalog und OpenAPI-Vertrag werden in derselben Änderung nachgeführt. - Der MailService protokolliert synchrone Ablehnungen künftig mit dem SMTP-Antwortcode statt mit dem allgemeinen Nodemailer-Code.
- Unit-Tests verwenden anonymisierte Rücklaufmeldungen; der End-to-End-Test erfolgt mit dem Prüfwerkzeug
backend/scripts/test-bounce-e2e.ts(npm run bounce:e2e). - Ein Monitoring-Alarm für Meldungen im Ordner «Manuell-pruefen» folgt mit ADR-021.
- Ein späterer Providerwechsel mit Bounce-Webhooks bleibt möglich; die fachliche Regel zur Unzustellbarkeit von Einladungen bleibt davon unberührt.
Umsetzungsstatus¶
| Teil | Status |
|---|---|
Rücklauf an bounces@divelogix360.ch |
Nachgewiesen am 12.10.2026 |
| Runbook für den betrieblichen Ablauf | Bestätigt am 12.10.2026: Zuständigkeit technische Leitung, werktägliche Prüfung |
| Verknüpfung zwischen Versandprotokoll und Einladung (Migration) | Umgesetzt: Migration 20261012231500_uc00_notification_log_invitation_link am 12.10.2026 angewendet; MailService schreibt den Verweis |
| Einheitliche Fehlercodes für synchrone Ablehnungen | Umgesetzt: SMTP_<Antwortcode>; mit Unit-Tests geprüft |
| IMAP-Abruf und Auswertung | Umgesetzt (BounceProcessorService, bounce-parser.ts, imapflow 1.7.8, mailparser 3.9.38, beide MIT); 15 Unit-Tests mit anonymisierter Hostpoint-Meldung und IMAP-Attrappe erfolgreich (14.10.2026); im Betrieb mit BOUNCE_POLL_ENABLED=true zu aktivieren |
| Zustellstatus in der Programmierschnittstelle | Umgesetzt im Vertrag (GET /users/invitations, UC00-OpenAPI 1.1.0, API-UC00-62/63) und im Backend; Unit-Tests vorhanden, Anzeige in der Oberfläche folgt mit dem Frontend (UC08 BU-05) |
| Unit- und End-to-End-Tests | 59 Unit-Tests erfolgreich; End-to-End-Test mit npm run bounce:e2e am 14.10.2026 erfolgreich: Rücklaufmeldung zugeordnet, Versandeintrag bounced mit DSN_5.4.1, Meldung gelöscht (Nachweis Abschnitt 7) |
Verwandte ADRs¶
- ADR-016 – Hostpoint als E-Mail-Provider
- ADR-018 – Revisionsfähige Auditierung
- ADR-021 – Monitoring und Kostenkontrolle
- ADR-023 – Support-, Wartungs-, Fern- und Auslandszugriffe
Änderungshistorie¶
| Datum, Uhrzeit | Version | Änderung | Autor |
|---|---|---|---|
| 14.10.2026 09:17 | 1.7 | End-to-End-Test vom 14.10.2026 als erfolgreich nachgeführt; Prüfwerkzeug in den Konsequenzen auf test-bounce-e2e.ts korrigiert |
Claude |
| 14.10.2026 08:10 | 1.6 | Umsetzungsstatus nachgeführt: IMAP-Abruf und Auswertung umgesetzt und unit-getestet; End-to-End-Test offen | Claude |
| 14.10.2026 08:05 | 1.5 | Punkt 2: Datenbanksperre durch idempotente Verarbeitung und Sperre im Prozess ersetzt (Entscheid David Mittig vom 14.10.2026); Behandlung vorübergehender Meldungen präzisiert; Ordnername «Manuell-pruefen» ohne Umlaut für IMAP | Claude |
| 13.10.2026 14:26 | 1.4 | Auf @nestjs/schedule verzichtet; Zeitsteuerung mit Node.js-Bordmitteln (Entscheid David Mittig vom 13.10.2026) |
Claude |
| 13.10.2026 14:15 | 1.3 | Zustellstatus in Vertrag und Backend als umgesetzt nachgeführt | Claude |
| 13.10.2026 00:34 | 1.2 | Umsetzungsstatus nachgeführt: Migration angewendet, Einladungsverweis und SMTP-Fehlercodes umgesetzt | Claude |
| 12.10.2026 22:58 | 1.1 | Runbook im Umsetzungsstatus als bestätigt nachgeführt | Claude |
| 12.10.2026 22:29 | 1.0 | Verarbeitung von Rücklaufmeldungen mit betrieblichem Ablauf, automatischem IMAP-Abruf und Zuordnung zu Einladungen entschieden | Claude |