Files
archivmail/features/PROJ-8-imap-auto-sync.md
T
sysops 988c37d85d feat(PROJ-8): Automatischer IMAP-Sync (Cron-Scheduler)
Backend:
- internal/imap/store.go: 7 neue Felder (sync_interval_min, last_sync_at,
  last_sync_count, last_uid, sync_running, sync_status, sync_error_msg)
  DB-Migration via ALTER TABLE ADD COLUMN IF NOT EXISTS
  Neue Methoden: ListAll, UpdateSyncInterval, SetSyncRunning, UpdateSyncResult
- internal/imap/scheduler.go: Scheduler mit time.Ticker (1 min),
  inkrementeller Sync via UID SEARCH UID <lastUID+1>:*,
  exponential backoff (3 Versuche: 1s / 60s / 300s),
  sync_running-Flag verhindert parallele Syncs
- internal/api/server.go: POST /api/imap/{id}/sync (manueller Trigger),
  PATCH /api/imap/{id} (sync_interval_min setzen, 0 oder 5-1440 min)
- cmd/archivmail/main.go: Scheduler gestartet + via SetImap verdrahtet

Frontend:
- src/lib/api.ts: 6 neue ImapAccount-Felder, triggerImapSync, updateImapInterval
- src/app/imap/page.tsx: Intervall-Dropdown, "Sync jetzt"-Button,
  Letzter-Sync-Anzeige mit Status-Badge, Polling auch bei sync_running

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 02:17:44 +01:00

7.2 KiB
Raw Blame History

PROJ-8: Automatischer IMAP-Sync (Cron-Job)

Status: Deployed

Created: 2026-03-12 Last Updated: 2026-03-17

Dependencies

  • Requires: PROJ-3 (IMAP-Import) IMAP-Verbindungen müssen konfiguriert sein
  • Requires: PROJ-5 (Speicherung & Indexierung)

User Stories

  • Als Admin möchte ich ein Sync-Intervall konfigurieren (z.B. alle 15 Minuten), damit neue E-Mails automatisch archiviert werden.
  • Als Admin möchte ich den letzten Sync-Zeitpunkt und -Status pro IMAP-Verbindung sehen.
  • Als Admin möchte ich den Sync manuell auslösen können, damit ich nicht auf den nächsten Intervall warten muss.
  • Als System möchte ich beim Sync nur neue E-Mails (seit letztem Sync) abholen, damit kein unnötiger Traffic entsteht.

Acceptance Criteria

  • Sync-Intervall pro IMAP-Verbindung konfigurierbar (min. 5 Minuten, max. 24 Stunden)
  • IMAP UID-basierter inkrementeller Sync (nur neue E-Mails seit letztem Sync)
  • Admin-UI zeigt: letzter Sync, Status (Erfolg/Fehler), Anzahl importierter E-Mails
  • Manueller "Sync jetzt"-Button im Admin-Bereich
  • Bei Sync-Fehler: Retry mit exponential backoff (max. 3 Versuche)
  • Sync-Fehler nach allen Versuchen → Fehlermeldung im Admin-Dashboard

Edge Cases

  • IMAP-Server temporär nicht erreichbar → Retry ohne Abbruch des gesamten Sync-Jobs
  • Sync läuft noch wenn neuer Intervall beginnt → kein paralleler Sync für dieselbe Verbindung
  • E-Mails auf dem Server wurden gelöscht → im Archiv behalten (Archiv ist immutable)
  • Zeitzonenprobleme beim Datum-Vergleich → immer UTC intern verwenden

Technical Requirements

  • Kein externer Cron-Scheduler — time.NewTicker(1 * time.Minute) + Goroutine (YAGNI, keine neue Abhängigkeit)
  • Sync-Status persistent in DB gespeichert (überlebt Server-Neustart)

Implementation Notes (2026-03-17)

  • internal/imap/store.go: Account-Struct um 7 Sync-Felder erweitert; migrationSQL mit ADD COLUMN IF NOT EXISTS; neue Methoden: ListAll, UpdateSyncInterval, SetSyncRunning, UpdateSyncResult; einheitliche scanRow(scanner)-Funktion mit eigenem Interface statt pgx.Row
  • internal/imap/scheduler.go: Neues Paket; Scheduler mit sync.Mutex-geschützter running-Map; Start/Stop/TriggerSync; runSyncWithRetry mit 3 Versuchen (Backoffs: 1s, 60s, 300s); doSync delegiert storeAndIndex an den vorhandenen Importer
  • internal/api/server.go: imapScheduler-Feld; SetImap-Signatur erweitert; neue Routen POST /api/imap/{id}/sync und PATCH /api/imap/{id}
  • src/lib/api.ts: ImapAccount um 6 Felder erweitert; triggerImapSync, updateImapInterval hinzugefügt
  • src/app/imap/page.tsx: Polling auch für sync_running; Dropdown für Sync-Intervall; "Sync jetzt"-Button; Sync-Status-Badge + letzter Sync-Zeitstempel pro Account-Card
  • cmd/archivmail/main.go: NewScheduler, Start, Stop, SetImap mit Scheduler verdrahtet

Tech Design (Solution Architect)

Komponentenstruktur

Next.js Frontend (Admin-Bereich):

/admin/imap (integriert in IMAP-Verbindungsliste aus PROJ-3)
└── VerbindungsCard (pro Konto)
    ├── Sync-Intervall (Dropdown: 5min / 15min / 1h / 6h / 24h)
    ├── Letzter Sync: Zeitpunkt + Status (✓ OK / ✗ Fehler)
    ├── Anzahl importierter Mails beim letzten Sync
    ├── Fehlermeldung (wenn letzter Sync fehlgeschlagen)
    └── [Sync jetzt] Button

Go Backend:

Sync-Scheduler (startet beim Binary-Start)
├── Cron-Loop                        ← prüft jede Minute alle IMAP-Accounts
│   └── Für jeden Account:
│       ├── Intervall abgelaufen? → Sync-Worker starten
│       └── Sync läuft bereits? → überspringen (kein Parallel-Sync)
│
├── Sync-Worker (pro Account, Goroutine)
│   ├── IMAP verbinden (gleicher Client wie PROJ-3)
│   ├── Letzte bekannte UID aus DB laden
│   ├── UID SEARCH UID <last_uid>:* → nur neue Mails
│   ├── FETCH neue Mails
│   ├── → Storage Coordinator (PROJ-5)
│   ├── Letzte UID + Zeitstempel in DB speichern
│   └── Bei Fehler: Retry mit Exponential Backoff
│       (1. Versuch: sofort, 2.: +1min, 3.: +5min → dann Fehler)
│
└── POST /api/admin/imap/{id}/sync   ← manueller Trigger
    └── Sync-Worker sofort starten (ignoriert Intervall)

Sync-Fluss

Cron-Loop (jede Minute)
        │
        └── Account "Firmen-Postfach"  Intervall: 15 min
              last_sync_at = vor 16 Minuten → fällig
              sync_running = false → starten
                      │
                      ▼
              IMAP verbinden
                      │
                      ▼
              last_uid = 4821 (aus DB)
              UID SEARCH UID 4822:*
              → [4822, 4823, 4830, 4831]   (4 neue Mails)
                      │
                      ▼
              FETCH 4822:4831 RFC822
                      │
                      ▼
              Für jede Mail:
                Duplikat? → überspringen
                → Storage Coordinator
                      │
                      ▼
              last_uid = 4831 in DB speichern
              last_sync_at = NOW() (UTC)
              sync_status = "ok"
              sync_count = 4

Exponential Backoff bei Fehlern

Sync-Fehler (z.B. IMAP nicht erreichbar)
        │
        ├── Versuch 1: sofort          → Fehler
        ├── Versuch 2: +1 Minute      → Fehler
        ├── Versuch 3: +5 Minuten     → Fehler
        └── Aufgeben:
              sync_status = "error"
              error_msg = "Connection refused after 3 attempts"
              → Admin-Dashboard zeigt Fehler
              → nächster regulärer Intervall versucht es erneut

Datenmodell (Ergänzung zu imap_accounts)

Feld Beschreibung
sync_interval_min Sync-Intervall in Minuten (51440)
last_sync_at Zeitpunkt des letzten Syncs (UTC)
last_sync_count Anzahl importierter Mails beim letzten Sync
last_uid Höchste bekannte IMAP-UID (Startpunkt für nächsten Sync)
sync_running true wenn Sync gerade läuft (verhindert parallelen Sync)
sync_status ok / error / running
sync_error_msg Letzte Fehlermeldung

Technische Entscheidungen

Entscheidung Begründung
UID-basierter inkrementeller Sync Nur neue Mails seit letzter bekannter UID werden abgeholt minimaler Traffic, kein Re-Download
sync_running-Flag in DB Verhindert parallelen Sync derselben Verbindung auch nach Server-Neustart
Cron-Loop jede Minute Einfacher als individuelle Timer pro Account skaliert auf viele Accounts ohne Overhead
Exponential Backoff Temporäre Ausfälle (Netz, Server-Neustart) werden automatisch überbrückt ohne Admin-Eingriff
Status persistent in DB Server-Neustart verliert keinen Sync-Fortschritt Scheduler macht nahtlos weiter
Manueller Trigger Admin kann sofortigen Sync anstoßen ohne auf Intervall zu warten

Abhängigkeiten

Paket Zweck
github.com/robfig/cron Eingebetteter Cron-Scheduler
github.com/emersion/go-imap IMAP-Client (bereits PROJ-3)

QA Test Results

To be added by /qa

Deployment

To be added by /deploy