988c37d85d
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>
7.2 KiB
7.2 KiB
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;migrationSQLmitADD COLUMN IF NOT EXISTS; neue Methoden:ListAll,UpdateSyncInterval,SetSyncRunning,UpdateSyncResult; einheitlichescanRow(scanner)-Funktion mit eigenem Interface stattpgx.Rowinternal/imap/scheduler.go: Neues Paket;Schedulermitsync.Mutex-geschützterrunning-Map;Start/Stop/TriggerSync;runSyncWithRetrymit 3 Versuchen (Backoffs: 1s, 60s, 300s);doSyncdelegiertstoreAndIndexan den vorhandenenImporterinternal/api/server.go:imapScheduler-Feld;SetImap-Signatur erweitert; neue RoutenPOST /api/imap/{id}/syncundPATCH /api/imap/{id}src/lib/api.ts: ImapAccount um 6 Felder erweitert;triggerImapSync,updateImapIntervalhinzugefügtsrc/app/imap/page.tsx: Polling auch fürsync_running; Dropdown für Sync-Intervall; "Sync jetzt"-Button; Sync-Status-Badge + letzter Sync-Zeitstempel pro Account-Cardcmd/archivmail/main.go:NewScheduler,Start,Stop,SetImapmit 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 (5–1440) |
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