User-Feedback nach Live-Test:
Frontend:
- FAB raus, Plus-Button zurück in den Account-Liste-Section-Header
(`add-circle-outline` in brandOrange + Label "Postfach hinzufügen").
FAB stört am unteren Rand, oben passt zum iOS-NavBar-Pattern.
- Half-Donut Legend strikt max Top-3 + "Sonstige" — Threshold von ≤4
auf ≤3 gesenkt. Auch bei 4 Connections wird jetzt schon komprimiert.
- Hero-Donut-Subtitle "über N Postfächer" entfernt — Title-Block ist
jetzt eine Zeile: "XX blockiert · ● Live"
- Activity-Log default-collapsed war schon richtig (kein Change)
- Activity-Item-Redesign: x-Icon-Pille raus, Zeit + Provider als
Sub-Zeile unter dem Subject ("vor 2h · GMX"), kein Zeit-Label rechts mehr
Bug-Fix — NaNd in Activity-Row:
- Root-Cause: snake_case/camelCase-Mismatch. Backend liefert
`receivedAt`, `senderEmail`, `senderName`, `connectionId` (camelCase),
Frontend-Type hatte snake_case → undefined-Werte → `new Date(undefined)`
→ NaN → "NaNd"-Render
- MailBlockedItem-Type auf camelCase umgestellt + nested `connection`-Objekt
(passt jetzt zum Backend-Response)
- formatDate mit Number.isFinite-Guard — gibt null zurück bei ungültigem
Datum statt NaN-String zu rendern
Backend (imap-idle daemon):
- Daemon schreibt jetzt unmittelbar nach `client.connect()` einen Heartbeat
(last_idle_heartbeat_at = NOW()) + clear last_connect_error parallel
- Vorher: User sah 2-9min lang "wartet auf erste verbindung" obwohl
Connection längst aktiv war (Heartbeat kam erst beim ersten NOOP-Cycle)
- Re-Connect-Pfad nach AUTHENTICATIONFAILED ist automatisch mit
abgedeckt (geht durch denselben connect-Block)
- ESM-Daemon, kein Build-Step — Pipeline scp + pm2-restart reicht
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rebreak-imap-idle
Standalone IMAP IDLE Daemon für Rebreak.
Was er macht
- Hält pro aktiver
MailConnection(DB) eine persistente IMAP-IDLE-Session - Reagiert in Echtzeit auf
EXISTS-Events (neue Mail im Postfach) - Feuert bei jedem Event
POST /api/mail/scan-internalgegen das lokale Backend - Das Backend entscheidet ob und welche Mails gelöscht werden (Gambling-Keywords + Blocklist)
- Aktualisiert alle 5 min die Connection-Liste (neue User → neue Sessions, entfernte → geschlossen)
- IDLE wird alle 25 min erneuert (RFC 3501 Server-Timeout liegt bei 29 min)
Env-Vars
| Variable | Pflicht | Beschreibung |
|---|---|---|
DATABASE_URL |
ja | Postgres-Connection-String (Supabase Pooler oder direkt) |
ADMIN_SECRET |
ja | Shared Secret für /api/mail/scan-internal Header |
ENCRYPTION_KEY |
ja | AES-256 Key (identisch zum Backend-Key, 32+ Zeichen) |
BACKEND_URL |
nein | Default: http://127.0.0.1:3016 (staging) / 3015 (prod) |
NODE_ENV |
nein | production → BACKEND_URL default port 3015 |
Lokal starten (Entwicklung)
cd backend/imap-idle
npm install
DATABASE_URL=<...> ADMIN_SECRET=<...> ENCRYPTION_KEY=<...> node index.mjs
Via Infisical (analog zu start-staging.sh):
infisical run --env=staging -- node index.mjs
PM2 (Produktion)
Wird via ecosystem.config.js gestartet — siehe docs/internal/MAIL_DAEMON_DEPLOYMENT.md.
Logs (pm2)
[idle/<email>] connected (imap.gmail.com:993)
[idle/<email>] exists-event received (new mail)
[idle/<email>] scan-triggered → scanned=12 blocked=1
[idle/<email>] idle renewing (25min threshold)
[idle/<email>] reconnecting in 5s (attempt 2)
[idle/db] refreshed — 47 active connections, 47 sessions