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>
164 lines
7.2 KiB
Markdown
164 lines
7.2 KiB
Markdown
# 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 (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_
|