Initial Email-Forwarder scaffold

This commit is contained in:
DonVoo 2026-07-05 17:58:25 +02:00
commit 91e52aecd0
22 changed files with 1007 additions and 0 deletions

115
Codex-Briefing.md Normal file
View file

@ -0,0 +1,115 @@
# Codex-Briefing — Email-Forwarder
Du baust ein Go-Single-Binary im Hausstil (wie WatchDove): `main.go` als
Orchestrator, nummerierte `backend/NN-*.go`, Server-gerendertes HTML + HTMX,
`config.json`, SQLite via `modernc.org/sqlite` (kein cgo). Das Gerüst steht;
fülle die `// TODO Codex`-Stellen. **Lies zuerst `Projektbeschreibung.md` die
5 harten Regeln sind bindend.**
## Bibliotheken (lean, keine Lawine)
- IMAP: `github.com/emersion/go-imap/v2` (+ `imapclient`)
- Parsen (Message-ID, Header, Text/HTML für Viewer): `github.com/emersion/go-message`
- SQLite: `modernc.org/sqlite`
- POP3-Fallback: `github.com/knadh/go-pop3`
- SMTP-Weiterleiten: `net/smtp` (stdlib) — keine Extra-Dependency
`go mod tidy` + `go mod vendor` (Offline-Bunker, wie im Template).
## Modul-Landkarte
| Datei | Inhalt |
|---|---|
| `main.go` | Flags (`--run`, `--watch`), LoadConfig→ConnectDB→InitAuth, Web oder CLI |
| `backend/00-router.go` | Routen + `renderShell` (Outlook-2013-Dreispalter) — **da steht schon das Layout** |
| `backend/01-config.go` | `config.json` (App + Forward-SMTP). Konten NICHT hier, sondern DB |
| `backend/02-database.go` | Schema + Account-CRUD + `copied`-Cache (Idempotenz) |
| `backend/03-auth.go` | Login + `AuthMiddleware` |
| `backend/04-imap-source.go` | Quelle: Ordnerbaum + Fetch `BODY[] FLAGS INTERNALDATE` |
| `backend/05-imap-target.go` | Ziel: `EnsureFolder` + `Append` (Flags + INTERNALDATE) |
| `backend/06-mbox.go` | mbox schreiben (Umzug) **und** lesen (Viewer) |
| `backend/07-migrate.go` | Motor: pro Konto/Ordner/Mail, Dedup, Doppel-Write, Fortschritt |
| `backend/08-viewer.go` | mbox im Browser: Liste, Lesen, Weiterleiten |
| `backend/09-smtp.go` | SMTP nur fürs Weiterleiten |
| `backend/10-pop3.go` | POP3-Fallback-Quelle (nur INBOX, nie DELE) |
## DB-Schema (SQLite)
```
accounts(id, name,
src_host, src_port, src_security, src_insecure, src_user, src_pass, src_proto,
dst_host, dst_port, dst_security, dst_insecure, dst_user, dst_pass,
mbox_dir, active)
folder_map(id, account_id, src_folder, dst_folder) -- optional, Vorrang vor Rollen-Automatik
copied(account_id, folder, message_id UNIQUE) -- Delta-Cache
jobs(id, account_id, started, finished, total, done, errors, state)
```
`src_security`/`dst_security` = `"tls"` | `"starttls"` | `"none"`.
`src_insecure`/`dst_insecure` = ungültige Zerts akzeptieren.
## Der Umzugs-Kern (07-migrate.go), Schritt für Schritt
1. Quelle öffnen: `src_proto=imap``OpenIMAPSource`, sonst `OpenPOP3Source`.
2. `Folders()` → rekursiver Ordnerbaum.
3. Pro Ordner: Zielordner via `MapSourceToTarget` (11-folders.go) bestimmen —
Rollen-Zuordnung gegen die vorhandenen Ziel-Ordner (keine Dubletten),
Trenner umhängen, `folder_map` als Override. Dann `EnsureFolder`.
4. `Fetch(folder)` streamend. Pro Mail: `AlreadyCopied(id,folder,msgID)`?
→ ja: überspringen. Nein: **beide** schreiben — `TargetMailbox.Append`
(IMAP, mit Flags+INTERNALDATE) **und** `MboxWriter.Append` (lokal), dann
`MarkCopied`. Fehler pro Mail zählen, nicht den ganzen Lauf abbrechen.
5. `jobs` fortschreiben (Browser pollt `/migrate/status` alle 3 s).
6. `--watch`: Schleife mit Pause; dank `copied` kopiert jeder Durchlauf nur Neues.
## UI — Outlook 2013 (Vorlage)
Look sitzt schon in `frontend-js/dist/style.css` + `renderShell`:
**Ribbon** (Outlook-Blau `#0072c6`) mit Reitern *Umzug · Postfächer · Betrachten
· Einstellungen*, darunter **Dreispalter**: links Ordnerbaum (Konto → Ordner),
Mitte Nachrichtenliste (ungelesen = fett blau), rechts Lesebereich; unten
Statusleiste. Flach, Segoe UI, blaue Auswahlzeile `#cde6f7`. Panes per HTMX
nachladen (`#tree` / `#list` / `#read`). Klassen stehen im CSS: `.tree-node`,
`.msg-row(.unread/.active)`, `.read-*`, `.grid/.input/.btn`.
Reiter **Postfächer** = Konten-CRUD (Formular wie WatchDove `targetForm`, Quelle-
und Ziel-Block, „Test"-Button prüft beide Logins). Reiter **Umzug** = Konten
starten + Fortschritt. Reiter **Betrachten** = mbox-Viewer, im Lesebereich ein
„Weiterleiten"-Button (öffnet Formular → `09-smtp.go`).
`frontend-js/dist/htmx.min.js` per Bun holen (wie Template) und mit einchecken.
## Sonderfälle — die drei Umzugs-Fallen (zwingend)
1. **Unverschlüsselte Alt-Provider.** Verbinden nach `src_security`:
`tls`=DialTLS (993), `starttls`=DialStartTLS (143), `none`=Dial (reiner
Klartext). `src_insecure``tls.Config{InsecureSkipVerify:true}` für
kaputte/selbstsignierte Zerts. Muss ohne TLS funktionieren — alte Hoster
können oft nichts anderes. (04/05-imap-*.go, POP3 analog 110/995.)
2. **Sonderzeichen in Ordnernamen.** IMAP-Ordner sind auf der Leitung
modified-UTF-7 (`Gelöschte``Gel&APY-schte`). go-imap dekodiert/kodiert das —
intern IMMER mit dem Klartext-Namen arbeiten, **nie doppelt kodieren**.
Hierarchie-Trenner (`.` vs `/`) je Server aus LIST lesen und beim Umhängen
übersetzen (`11-folders.go`).
3. **Standardordner nicht doppeln.** `11-folders.go` bildet jeden Ordner auf
eine Rolle ab (SPECIAL-USE `\Sent \Drafts \Junk \Trash \Archive`, sonst
mehrsprachige Namensliste). `MapSourceToTarget` sortiert Gesendet/Entwürfe/
Spam/Papierkorb/Archiv/Posteingang in den **vorhandenen** Rollen-Ordner des
Ziels statt „Gesendete Objekte" **neben** „Gesendete Elemente" zu legen.
Manuelles `folder_map` schlägt die Automatik.
## Sicherheit
Tool hält fremde IMAP-Passwörter und kann Mails senden. Default `bind=127.0.0.1`
+ Login. Falls je remote (hinter Caddy): `/vadmin`-Mail-2FA-Muster aus dem
Web-Deploy-Kit vorschalten. `config.json`/DB nie committen (`.gitignore` steht).
## Fertig-Kriterien (Durchstich zuerst)
1. **Durchstich:** ein Konto, nur `INBOX`, Quelle→Ziel-`APPEND` + mbox,
Message-ID-Dedup, zweimal laufbar ohne Dubletten.
2. Alle Ordner rekursiv + `EnsureFolder` im Ziel.
3. Konten-CRUD + Test-Button im Browser.
4. Viewer (Liste/Lesen) + Weiterleiten.
5. `--watch`-Delta-Schleife.
6. Optional: POP3-Fallback.