# MAIL_DAEMON_DEPLOYMENT — Backyard Handoff **Erstellt von:** Mo (Mail-Architektur-Agent) **Datum:** 2026-05-09 **Status:** Bereit für Deployment — wartet auf Backyard-GO vom User ## Kontext Der `rebreak-imap-idle` Daemon ist ein eigenständiger Node.js-Prozess. Er hält pro aktivem MailConnection-DB-Eintrag eine persistente IMAP-IDLE-Session und triggert bei neuer Mail sofort `/api/mail/scan-internal` statt auf den 30min-Cron zu warten. Der Daemon liegt unter `backend/imap-idle/index.mjs` und hat seine eigene `package.json`. Er ist KEIN Teil des Nitro-Builds — er wird direkt via `node` gestartet. ## Was Backyard tun muss (in dieser Reihenfolge) ### Schritt 1 — GH-Actions: imap-idle ins Artifact einschließen In `.github/workflows/deploy-backend.yml` (oder analog) muss das `backend/imap-idle/`-Verzeichnis ins deploy-Artifact aufgenommen werden. Das Artifact enthält aktuell wahrscheinlich nur `backend/.output-staging/` und `backend/prisma/`. `backend/imap-idle/` muss ebenfalls mit kopiert werden. Konkretes Beispiel (je nach Artifact-Aufbau anpassen): ```yaml # In der scp/rsync-Step des deploy-workflows: - name: Copy imap-idle to server run: | scp -r backend/imap-idle/ rebreak-server:/srv/rebreak/backend/imap-idle/ ``` ### Schritt 2 — npm install auf Server Nach dem Artifact-Copy muss auf dem Server `npm install` in `backend/imap-idle/` laufen. Das installiert `imapflow` und `pg` lokal für den Daemon. ```bash cd /srv/rebreak/backend/imap-idle && npm install --production ``` Diesen Schritt als deploy-Step in GH-Actions oder in `deploy-from-artifact.sh` ergänzen. ### Schritt 3 — Zombie-Prozesse aufräumen Vor dem ersten Start der neuen pm2-Einträge alte Stale-Einträge entfernen (falls `rebreak-imap-staging` oder `rebreak-idle-staging` aus altem Setup noch existieren): ```bash pm2 delete rebreak-idle-staging rebreak-imap-staging 2>/dev/null || true ``` ### Schritt 4 — ecosystem.config.js erweitern Die folgenden Einträge in `/srv/rebreak/ecosystem.config.js` ergänzen (unterhalb des bestehenden `rebreak-staging`-Eintrags einfügen): ```js // ─── IMAP IDLE Daemon Staging ─────────────────────────────────────────────── { name: "rebreak-idle-staging", script: "/srv/rebreak/backend/imap-idle/index.mjs", interpreter: "/root/.nvm/versions/node/v24.11.1/bin/node", cwd: "/srv/rebreak/backend/imap-idle", instances: 1, autorestart: true, watch: false, max_memory_restart: "256M", env: { NODE_ENV: "production", // ACHTUNG: Keine Secrets hier hinterlegen. // Infisical-Wrapper via start-idle-staging.sh (Schritt 5). }, }, // ─── IMAP IDLE Daemon Prod (auskommentiert bis Prod-Cutover) ─────────────── // { // name: "rebreak-idle-prod", // script: "/srv/rebreak/backend/imap-idle/index.mjs", // interpreter: "/root/.nvm/versions/node/v24.11.1/bin/node", // cwd: "/srv/rebreak/backend/imap-idle", // instances: 1, // autorestart: true, // watch: false, // max_memory_restart: "256M", // env: { NODE_ENV: "production" }, // }, ``` ### Schritt 5 — start-idle-staging.sh erstellen Der Daemon braucht die gleichen Infisical-Secrets wie das Backend. Eine eigene Start-Shell analog zu `backend/start-staging.sh` erstellen: Pfad: `/srv/rebreak/backend/imap-idle/start-idle-staging.sh` ```bash #!/bin/bash # rebreak-imap-idle Staging — Infisical-Secret-Injection set -euo pipefail source /etc/environment if [[ -z "${INFISICAL_CLIENT_ID:-}" || -z "${INFISICAL_CLIENT_SECRET:-}" ]]; then echo "[idle] FEHLER: INFISICAL_CLIENT_ID / SECRET nicht gesetzt" >&2 exit 1 fi INFISICAL_TOKEN=$(infisical login \ --method=universal-auth \ --client-id="${INFISICAL_CLIENT_ID}" \ --client-secret="${INFISICAL_CLIENT_SECRET}" \ --silent --plain 2>/dev/null) [[ -z "$INFISICAL_TOKEN" ]] && { echo "[idle] Infisical login fehlgeschlagen" >&2; exit 1; } NODE_BIN="/root/.nvm/versions/node/v24.11.1/bin/node" DAEMON="/srv/rebreak/backend/imap-idle/index.mjs" exec infisical run \ --projectId="${INFISICAL_PROJECT_ID:-14b11b35-ef59-4b8a-a16b-398f0cc3ad93}" \ --env=staging \ --token="$INFISICAL_TOKEN" \ -- bash -c ' set -e export DATABASE_URL="${DATABASE_URL:-${NUXT_DATABASE_URL:-}}" export ADMIN_SECRET="${ADMIN_SECRET:-${NUXT_ADMIN_SECRET:-}}" export ENCRYPTION_KEY="${ENCRYPTION_KEY:-${NUXT_ENCRYPTION_KEY:-}}" export BACKEND_URL="http://127.0.0.1:3016" exec '"$NODE_BIN"' '"$DAEMON"' ' ``` Dann `chmod +x start-idle-staging.sh` und in ecosystem.config.js den `script`-Key auf `start-idle-staging.sh` zeigen lassen (mit `interpreter: "bash"`), analog zum Pattern von `rebreak-staging`. ### Schritt 6 — pm2 starten ```bash pm2 startOrReload /srv/rebreak/ecosystem.config.js ``` ## Verifikations-Schritte nach Deployment ```bash # 1. pm2-Status prüfen pm2 list # Erwartung: rebreak-idle-staging → status=online, restart=0 # 2. Logs der ersten 60 Sekunden ansehen pm2 logs rebreak-idle-staging --lines 100 # Erwartung: "[idle/] connected (...)" für alle aktiven Mailboxen # "[idle/db] refreshed — N active connections, N sessions" # 3. Test: Mail an eine verbundene Mailbox schicken (Betreff: "casino bonus") # Innerhalb von 5 Sekunden sollte im Log erscheinen: # "[idle/] exists-event received (new mail)" # "[idle/] scan-triggered → scanned=X blocked=1" # 4. Memory-Check nach 10 Minuten pm2 monit # Erwartung: < 100MB bei <20 Connections, < 200MB bei 100 Connections ``` ## Rollback-Plan Falls der Daemon crashed oder instabil ist: ```bash pm2 stop rebreak-idle-staging # NICHT delete — damit Logs erhalten bleiben ``` Auswirkung: Mail-Scanning fällt auf den bestehenden 30min-Cron zurück. Kein Komplett-Ausfall der Mail-Schutz-Funktion. Gambling-Mails werden weiter gelöscht, nur mit bis zu 30min Verzögerung statt Echtzeit. ## Bekannte Provider-Quirks | Provider | IMAP-Host | Port | TLS | Bekanntes Problem | |-------------|-----------------------------|------|----------|--------------------------------------------| | Gmail | imap.gmail.com | 993 | Implicit | App-Password erforderlich (kein OAuth2) | | iCloud | imap.mail.me.com | 993 | Implicit | App-Specific-Password in Apple-Settings | | Outlook | outlook.office365.com | 993 | Implicit | IDLE-Drop nach ~20min — disableCompression | | GMX | imap.gmx.net | 993 | Implicit | Stabil, kein besonderer Quirk | | Web.de | imap.web.de | 993 | Implicit | Stabil | | T-Online | secureimap.t-online.de | 993 | Implicit | Stabil | | Posteo | posteo.de | 993 | Implicit | Stabil | Outlook-spezifisch: Der Daemon setzt `disableCompression: true` wenn der Host `office365` enthält — verhindert partial-read-Fehler nach IDLE-Drop. ## Datei-Übersicht ``` backend/imap-idle/ index.mjs — Daemon-Hauptdatei (ESM, standalone) package.json — Eigene Dependencies (imapflow, pg) README.md — Kurz-Doku (lokal starten, env-vars, log-format) ``` ## Abhaengigkeiten - `imapflow ^1.2.18` — IMAP-Client-Library (bereits in backend/package.json) - `pg ^8.16.3` — Direkter Postgres-Zugriff (kein Prisma im Daemon-Kontext) - Node.js >= 20 (ESM, top-level await via main()) - Infisical-CLI auf dem Server (bereits installiert fuer rebreak-staging)