Adds 3 fields to mail_connections so UI can distinguish between
"connection alive but no new mail" vs "connection dead" vs "auth-failed":
- last_connect_error — text of last IMAP error (auth-fail, connect-fail)
- last_connect_error_at — timestamp of error
- last_idle_heartbeat_at — updated every 2min by NOOP-success in daemon
Daemon (backend/imap-idle/index.mjs):
- updateConnectionError() / clearConnectionError() / updateIdleHeartbeat()
SQL helpers
- logError now uses err.responseText (shows "AUTHENTICATIONFAILED" instead
of generic "Command failed")
- clearError on connect() success
- updateError on connect() catch
- updateHeartbeat in NOOP-success-path (every 2min)
API (status.get.ts): returns the 3 new fields per account.
Migration: ALTER TABLE rebreak.mail_connections ADD COLUMN ... (idempotent).
UI-side (in flight, separate task): MailAccountCard renders auth-error
banner when lastConnectError != null + heartbeat-based "live" indicator.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User-test: Casino-mail an Chahine@gmx.net wurde nicht geblockt obwohl
daemon "connected" zeigte. Mo's diagnose: GMX dropped IDLE-connection
silent (kein TCP-error, kein logout). ImapFlow.idle() hängt unbegrenzt
ohne reject — exists-events kommen nie an, daemon ist faktisch tot.
2 Fixes:
1) IDLE_RENEW_INTERVAL_MS: 25 min → 10 min. GMX timeout-window ist
~10-15min, 25min war zu lang. Trade-off: alle 10min full reconnect.
2) NOOP-heartbeat alle 2min während IDLE-loop. Wenn NOOP fail
(= silent-drop detected) → close → reconnect-loop. Early-detection.
Andere provider (Gmail/iCloud/Outlook) sind unaffected — die haben
~29min IDLE-timeout, also passt 10min auch dort safe.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Daemon SQL used PascalCase "MailConnection" + camelCase column-names
that match the Prisma model field-names — but actual DB has snake_case
table "mail_connections" with snake_case columns (per @map decorators).
Result: daemon was online but ALL queries failed with
relation "rebreak.MailConnection" does not exist
→ no mailboxes loaded → no IDLE-sessions established.
Fix: query "rebreak.mail_connections" with snake_case columns, alias
back to camelCase via SQL AS so rest of the daemon code works unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Standalone ESM-daemon that:
- Connects via ImapFlow IDLE to all active Legend mailboxes
- Triggers /api/mail/scan-internal on new-mail events (real-time)
- Auto-renew IDLE every 25min (RFC 3501 limit), exponential-backoff reconnect
- DB-refresh every 5min for new/removed connections
Plus deploy-pipeline:
- GH-Actions artifact-upload + scp to /srv/rebreak/backend/imap-idle/
- npm install --production on server (imapflow + pg)
- pm2 startOrReload via ecosystem.config.js
- start-idle-staging.sh wrapper for Infisical secret-injection
Replaces 30min-cron polling for Legend tier -- Casino-mails now blocked
within seconds, fulfilling Legend tier marketing promise.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>