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

164 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
- [x] Sync-Intervall pro IMAP-Verbindung konfigurierbar (min. 5 Minuten, max. 24 Stunden)
- [x] IMAP UID-basierter inkrementeller Sync (nur neue E-Mails seit letztem Sync)
- [x] Admin-UI zeigt: letzter Sync, Status (Erfolg/Fehler), Anzahl importierter E-Mails
- [x] Manueller "Sync jetzt"-Button im Admin-Bereich
- [x] Bei Sync-Fehler: Retry mit exponential backoff (max. 3 Versuche)
- [x] 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_