Zum Inhalt

ADR-024: Verarbeitung von Rücklaufmeldungen (Bounces)

Stand: 14.10.2026 09:17 Branch: main Status: Akzeptiert

Änderungshistorie anzeigen


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:

  1. Synchrone Ablehnung: Hostpoint lehnt die Empfängeradresse bereits beim Versand ab, zum Beispiel mit 550 unroutable address. Der MailService erhält den Fehler direkt.
  2. 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) an bounces@divelogix360.ch. Die Meldung enthält die Message-ID der Originalnachricht im Kopf References: 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

  1. Das Runbook Bounce-Postfach regelt Zuständigkeit, Prüfintervall und Vorgehen für Meldungen, die nicht automatisch verarbeitet werden können.
  2. Verarbeitete und manuell bearbeitete Rücklaufmeldungen werden unverzüglich gelöscht, weil sie die vollständige Originalnachricht einschliesslich Einladungslink enthalten.

Automatische Verarbeitung

  1. Ein Dienst im Backend ruft bounces@divelogix360.ch per 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.
  2. 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.
  3. Verarbeitet werden nur Meldungen mit multipart/report; report-type=delivery-status. Als endgültig unzustellbar gilt Action: failed mit Status 5.x.x. Vorübergehende Fehler (4.x.x) und Verzögerungsmeldungen ändern keinen Status und werden gelöscht.
  4. Die Zuordnung erfolgt über die Message-ID der Originalnachricht aus References:, ersatzweise aus der angehängten Originalnachricht, gegen notification_log.provider_message_id.
  5. Bei einem Treffer werden status = bounced, failed_at und error_code = DSN_<Code> gesetzt. Der Code stammt aus Diagnostic-Code, ersatzweise aus Status, weil Hostpoint in Status teilweise nur den allgemeinen Code 5.0.0 meldet.
  6. Synchrone Ablehnungen werden einheitlich als error_code = SMTP_<Antwortcode> protokolliert. Ein 5xx-Code gilt ebenfalls als endgültig unzustellbar.
  7. Empfängeradressen, Diagnosetexte und Nachrichteninhalte werden nicht gespeichert.
  8. Meldungen ohne Treffer oder ohne Standardformat werden in den Ordner «Manuell-pruefen» verschoben und als Warnung ohne personenbezogene Daten im Systemprotokoll vermerkt.
  9. Die Verarbeitung läuft im Plattformkontext (dlx_app_platform). Für die Tenant-Rolle bleibt notification_log unverändert nur anhängbar.

Zuordnung zu Einladungen

  1. notification_log erhält ein optionales Feld invitation_token_id, das auf die versendete Einladung verweist. Die Verknüpfung ist wie die übrigen Referenzen tenantgebunden.
  2. Der Zustellstatus einer Einladung wird aus ihrem letzten Versandeintrag abgeleitet. Eine Einladung gilt als unzustellbar, wenn dieser Eintrag bounced ist oder failed mit einem endgültigen Fehlercode.
  3. Der Lebenszyklus der Einladung (pending, used, revoked, expired) bleibt unverändert. Ein zusätzlicher Einladungsstatus wird nicht eingeführt.
  4. Die einladende Person erkennt den Zustellstatus über die Programmierschnittstelle; die Anzeige in der Benutzeroberfläche folgt mit dem Frontend.

Umgebungen

  1. Der Abruf ist über BOUNCE_POLL_ENABLED schaltbar und in der Entwicklung standardmässig deaktiviert.
  2. 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.
  3. 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_id mit 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

Ä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