# Betrieb, Provider und Zustellgarantien ## Start und Upgrade ```sh go run ./cmd/gateway -config ./data/config.json ``` Die Konfiguration bleibt JSON-Schema 1. Der Server legt `outbox.db` neben der Konfiguration an. Ein anderer Pfad lässt sich mit `-outbox /pfad/outbox.db` setzen. SQLite arbeitet ohne CGO. Das Verzeichnis muss schreibbar sein; dort entstehen auch `outbox.db-wal` und `outbox.db-shm`. **Verhaltensänderung:** HTTP-Eingänge antworten nach persistenter Annahme mit **202**, bevor ein Ziel kontaktiert wird: ```json {"ok":true,"receipt":{"id":"...","duplicate":false}} ``` Die Zustellhistorie zeigt den späteren Status. Speicherfehler führen zu 503, ungültige Nachrichten/fehlende Regeln zu 400. Vorschauen versenden und speichern keine Zustellungen. Verarbeitete Dry-Run-Aufträge erscheinen als `dry_run`. ## Deduplizierung und Retry HTTP-Sender sollten pro logischer Nachricht einen stabilen Schlüssel setzen: ```sh curl -X POST http://localhost:8080/in/webhook/default \ -H 'Authorization: Bearer replace-me' \ -H 'Idempotency-Key: monitoring-event-123' \ -H 'Content-Type: application/json' \ -d '{"title":"Server","message":"Dienst ausgefallen"}' ``` - Der Schlüssel gilt für Quelle und Kanal, maximal 256 Bytes. Derselbe Schlüssel und Inhalt liefern dieselbe Referenz mit `duplicate:true`. Anderer Inhalt mit demselben Schlüssel ergibt 409. Empfangszeitpunkte zählen nicht zum Inhalt. - Ohne Schlüssel ist jede HTTP-Anfrage eine neue Nachricht. Gleicher Text allein löst keine Deduplizierung aus: gleiche Meldungen können neue Ereignisse sein. - Alle passenden Regeln werden in einer Transaktion angenommen. Wenn eine kein gültiges Payload erzeugt, wird keine Teilmenge gespeichert. - Jeder Ausgang hat einen eigenen Auftrag. Erfolgreiche Aufträge werden nicht wegen des Fehlers eines anderen Ziels wiederholt. - HTTP-Verbindungsfehler, 408/425/429/5xx sowie temporäre SMTP-4xx-Fehler werden wiederholt: maximal acht Versuche, zunächst etwa fünf Sekunden Wartezeit, danach exponentiell mit Zufallsanteil. `Retry-After` wird bis 24 Stunden beachtet. - Nach dauerhaften Fehlern oder acht Fehlversuchen bleibt der Auftrag `dead`. **Erneut versuchen** startet einen neuen Versuchslauf desselben Auftrags mit ursprünglichem Ziel/Inhalt. Die vorherigen Versuche bleiben in der Historie. - Nach einem Absturz wird ein verwaister Versand nach Ablauf seiner zehnminütigen Reservierung wieder verfügbar. Regulärer Shutdown beendet laufende Netzwerkaufrufe und speichert deren Ergebnisse. HTTP-Ausgänge senden `Idempotency-Key` und `X-Notify-Gateway-Delivery` mit der stabilen Delivery-ID. SMTP verwendet eine stabile `Message-ID`. Fremde Provider müssen diese Merkmale selbst zur Deduplizierung unterstützen. Ein Absturz nach erfolgreichem Versand, aber vor dem lokalen Erfolgs-Commit, kann daher eine Doppelzustellung verursachen. Es gibt keine Exactly-once-Garantie. Ein Worker arbeitet sequenziell. Ein langsames Ziel verzögert andere Aufträge; Provider-Timeouts begrenzen die Dauer. Durchsatztests und konfigurierbare Parallelität für größere Installationen stehen noch aus. ## Ausgangsprovider Unter **Ausgänge** Verbindungen anlegen und unter **Zuordnungen** auswählen. Neue Verbindungen verwenden `live:false`; Divera247 hat weiterhin `dry_run:true`. Die Live-Einstellung wird beim Annehmen gespeichert. Das spätere Abschalten eines Ausgangs hält bereits angenommene Live-Aufträge nicht an. | Provider | Einstellungen | |---|---| | Discord | `https://discord.com/api/webhooks/ID/TOKEN`; Titel und Meldung, keine automatischen Mentions, maximal 2000 UTF-16-Codeeinheiten | | HTTP-Webhook | Vollständige HTTP(S)-URL, optionaler Bearer-Token; POST mit gemappten Nachrichtenfeldern | | ntfy | Server-Basis-URL, Topic, optionaler Bearer-Token; JSON-Publish, positive Priorität auf maximal 5 begrenzt | | Gotify | Vollständiger `/message`-Endpunkt und App-Token; `X-Gotify-Key`, Titel/Meldung/Priorität | | SMTP | Host, Port, `tls` (typisch 465) oder `starttls` (typisch 587), optional Benutzer/Passwort, reine Absender-/Empfängeradressen | SMTP verlangt eine gültige Zertifikatskette. `AUTH PLAIN` wird ausschließlich nach TLS verwendet. Mail-Inhalte sind UTF-8-Plain-Text als Base64-MIME; keine Anhänge. Quellen: [ntfy JSON-Publish](https://docs.ntfy.sh/publish/#publish-as-json), [Gotify Nachrichten](https://gotify.net/docs/pushmsg), [Discord Webhooks](https://docs.discord.com/developers/resources/webhook#execute-webhook). ## IMAP-Eingang Unter **Eingänge** einen Mail-Eingang einrichten: eindeutige ID, TLS-Adresse wie `imap.example.org:993`, Benutzer, Passwort, Ordner und Zuordnungskanal. Regeln verwenden `source: "mail"` und diesen Kanal. - Passwort- oder App-Passwort-Anmeldung; OAuth2 ist noch nicht implementiert. - Der Ordner wird schreibgeschützt geöffnet; Nachrichten werden weder gelöscht noch als gelesen markiert. Checkpoints liegen in SQLite. - Beim ersten erfolgreichen Abruf wird standardmäßig der aktuelle UID-Stand übernommen. **Vorhandene Nachrichten importieren** muss vor diesem ersten Abruf gesetzt sein, wenn alte Nachrichten verarbeitet werden sollen. - UIDVALIDITY-Wechsel setzt den Cursor zurück. Der neue UID-Namensraum wird erneut verarbeitet; alte Inhalte können dadurch erneut auftreten. Wechsel von Adresse/Benutzer/Ordner/Eingangs-ID erzeugt einen neuen Checkpoint und wendet die Erstimport-Einstellung erneut an. - Ein Abruf verarbeitet bis zu 100 neue Nachrichten. Checkpoints werden erst nach Outbox-Annahme oder bewusster Filterung fortgeschrieben. - Maximal 1 MiB Rohmail und dekodierter Text. Plain-Text-MIME wird verwendet; Anhänge werden ignoriert. HTML-only, fehlerhafte/zu große Nachrichten oder ein fehlendes Mapping blockieren den Checkpoint. Die Zustellhistorie zeigt den Abruffehler. Nachricht manuell verschieben oder eine passende Regel ergänzen. - Absender-/Empfängerfilter vergleichen Adressen ohne Beachtung der Großschreibung. Header beweisen keine Absenderidentität; diese Prüfung gehört zusätzlich auf den empfangenden Mailserver. - Eigene Gateway-Mails und `Auto-Submitted`-Nachrichten werden übersprungen, um Rückkopplungen zu vermeiden. HTTP-Rückläufer mit Gateway-Delivery-Header werden ebenfalls abgelehnt. ## Discord-Eingang 1. Discord-Anwendung anlegen, Public Key und Application-ID übernehmen. 2. Unter **Eingänge** Command-Name, erlaubte Guild-IDs und Channel-IDs setzen. 3. Slash-Command entsprechend [discord-command.example.json](discord-command.example.json) bei der Anwendung registrieren; der Name muss zur Konfiguration passen. 4. Als Interactions Endpoint die öffentliche HTTPS-Adresse `/in/discord` setzen. 5. Regel mit `source: "discord"` und Channel-ID als Kanal erstellen. PING und Commands werden mit Ed25519 geprüft; Zeitstempel dürfen maximal fünf Minuten abweichen. Application-ID und beide Allowlists müssen passen. `message` ist erforderlich, `title` und `priority` optional. Die private Antwort bestätigt die Speicherung, nicht den Versand. Es erfolgt kein Mithören beliebiger Channel-Nachrichten und keine automatische Registrierung externer Anwendungen. Siehe [Discord Interactions](https://docs.discord.com/developers/interactions/receiving-and-responding). ## Historie, Metriken und Sicherheit - **Zustellhistorie**: Seiten zu 50 Aufträgen, Status, Referenz, Fehlerklasse und letzte 200 Versuchsereignisse pro Auftrag. Keine Bodies oder Zugangsdaten. - `/healthz`: Prozess erreichbar. `/readyz`: SQLite erreichbar; keine Aussage zur Erreichbarkeit aller Provider. - `/metrics`: Prometheus-Gauge `notify_gateway_deliveries{state="..."}`. Zugriff mit `Authorization: Bearer ...`, wenn `GATEWAY_METRICS_TOKEN` gesetzt ist; sonst nur mit Admin-Session. - Schreibende Admin-APIs/Formulare benötigen einen Token aus `GET /ui/api/csrf` (`X-CSRF-Token` bzw. `csrf_token`). Die WebUI erledigt das automatisch. Zusätzlich werden fremde Origins abgelehnt. - Neue Passwort-Hashes verwenden bcrypt. SHA-256-Hashes werden beim erfolgreichen Login migriert; alte Passwörter über 72 Bytes bleiben bis zum Wechsel im Legacy-Format. Neue Passwörter: 10 bis 72 Bytes. Wechsel widerruft alle Sessions. - Login-Limit: zehn Versuche pro Minute und direkter Client-IP. Proxy-Header werden nicht vertraut; hinter einem Proxy eigenes Rate-Limit verwenden und den originalen Host erhalten. - Request-Limits: 1 MiB für Eingänge, 2 MiB für Admin-Aufrufe. CSP erlaubt das eingebettete UI-Skript per Hash; Admin-Antworten verwenden `no-store`. - Bearer-Token, SMTP-/IMAP-Passwort und Divera-Accesskey akzeptieren `env:VARIABLE`. Fehlende Variablen führen zu Fehlern. Outbox-Snapshots behalten die Referenz; ein später geänderter Variablenwert wird beim Versand verwendet. Konfiguration und Outbox enthalten sensible Daten; direkte Secrets und Inhalte werden nicht verschlüsselt. POSIX-Dateimodus ist `0600`, unter Windows gelten zusätzlich die geerbten ACLs. Verzeichniszugriff und Backups entsprechend schützen. HTTPS über einen Reverse Proxy bleibt erforderlich. ## Backup und Aufbewahrung Für ein konsistentes einfaches Backup Gateway regulär stoppen und das ganze Datenverzeichnis einschließlich Konfiguration, Datenbank und gegebenenfalls WAL/SHM-Dateien sichern. Nur die laufende `outbox.db` zu kopieren genügt nicht. Wiederherstellung bei gestopptem Gateway, anschließend starten. Historie und Deduplizierungsbelege werden unbegrenzt aufbewahrt; automatische Bereinigung fehlt noch. Größe überwachen. Tabellen nicht manuell löschen: Belege und Checkpoints sind für Wiederanlauf und Deduplizierung nötig.