# Hvis du vil skrive MailFetcher selv

Teknisk opskrift til en kodeagent. Implementér programmet uden adgang til MailFetcher-koden.

MailFetcher er et afgrænset program: læs mail fra IMAP, gem den komplette meddelelse som en lokal EML-fil, og rør ikke ved mailen på serveren.

Her er specifikationen, hvis du hellere vil bygge det selv. Den beskriver programmet og dets stopkriterier på én side. Vælg selv sprog og biblioteker, så længe adfærden nedenfor er den samme.

## 1. Opgaven

- Byg et selvstændigt kommandolinjeprogram til Linux, macOS og Windows. Det skal kunne køre én hentning og afslutte eller blive ved med at kontrollere flere konti med hvert sit interval.
- Hver konfigureret IMAP-mappe knyttes til én absolut lokal destinationsmappe. Første kørsel henter alle eksisterende mails, medmindre en positiv batchgrænse er sat. Senere kørsler henter kun nye UID'er.
- Gem serverens rå RFC 5322/MIME-meddelelse uændret som `.eml`, inklusive headers, multipart-dele og vedhæftninger. Programmet skal ikke rendere, normalisere eller udtrække indhold.
- Programmet må aldrig sende, markere som læst, flytte eller slette mail på serveren. Lokal efterbehandling af EML-filer er uden forbindelse til IMAP-serveren.

## 2. Konfiguration og kommandoer

Indlæs én privat JSON-fil via `--config`. Afvis ukendte felter, dublerede konto-ID'er, relative stier, ugyldige intervaller og manglende legitimationsoplysninger. På Unix må konfigurationsfilen ikke kunne læses af gruppe eller andre; tilstandsmappe og destinationsmapper skal oprettes privat. Læg aldrig hemmeligheder i repository eller log.

```json
{
  "state_dir": "/home/user/.local/state/mailfetcher",
  "accounts": [{
    "id": "work",
    "host": "imap.example.com",
    "port": 993,
    "tls": "implicit",
    "username": "user@example.com",
    "auth": "password",
    "password": "APP_PASSWORD",
    "poll_interval": "5m",
    "batch_size": 0,
    "folders": [{"remote": "INBOX", "destination": "/home/user/Mail/work"}]
  }]
}
```

Konto-ID må kun indeholde bogstaver, tal, understregning og bindestreg. `poll_interval` skal være mindst ét minut. `batch_size: 0` betyder ingen grænse. TLS kan være implicit TLS eller STARTTLS; login må først ske efter verificeret TLS. Tillad eventuelt en eksplicit PEM-CA-fil til private servere, men slå aldrig certifikatkontrol fra.

Implementér `run --config PATH --once`, `run --config PATH` og `authorize --config PATH --account ID`. `--once` skal forsøge alle konti og returnere fejlstatus, hvis mindst én fejler. Vedvarende drift skal respektere hvert konto-interval og kunne stoppes rent med SIGINT/SIGTERM. Kun én proces må eje samme tilstandsmappe ad gangen.

## 3. Forbindelse og IMAP-læsning

- Understøt password-login og Gmail OAuth 2.0 via XOAUTH2. Gmail-varianten skal bruge `imap.gmail.com:993` og implicit TLS. Autorisationen bruger en desktop-klient, browsergodkendelse og en lokal loopback-callback. Gem refresh token privat, hent nyt access token ved behov, og kræv ny autorisation ved tilbagekaldt grant. IMAP med Gmail kræver scope `https://mail.google.com/`.
- Åbn hver mappe med IMAP `EXAMINE`, aldrig `SELECT` i skrivbar tilstand. Kræv et gyldigt `UIDVALIDITY`. Find UID'er med `UID SEARCH ALL`; sortér dem numerisk og behandl dem i stigende rækkefølge.
- Hent en mail med `UID FETCH <uid> (UID BODY.PEEK[])`. Verificér, at svaret gælder det ønskede UID, kun indeholder én meddelelseskrop, og at den læste længde svarer til IMAP-literalens oplyste længde. Håndtér quoted mailbox-navne og IMAPs modified UTF-7 korrekt.
- Afvis et ufuldstændigt, uventet eller fejlbehæftet serversvar. Luk forbindelsen og lad næste kørsel genoptage fra den sidst færdige mail.

## 4. Filer, identitet og fremdrift

Identificér en mail med konto-ID, fjernmappens navn, `UIDVALIDITY` og UID. Lav deterministiske filnavne med disse værdier, så to mapper og to konti ikke kolliderer. Brug eventuelt et kort hash af mappenavnet i filnavnet. En lokalt slettet færdig fil skal ikke automatisk hentes igen, når dens UID allerede er registreret som færdig.

Skriv først til en midlertidig `.partial`-fil i destinationsmappen. Kontrollér antal bytes, flush filen til disk, luk den og omdøb den atomisk til `.eml`. Opdatér først derefter den private fremdriftsfil atomisk med seneste færdige UID. Afbrydes processen før omdøbning eller fremdriftsskrivning, må næste kørsel kunne gentage uden at efterlade en halv EML-fil som færdigt resultat.

Gem fremdrift pr. konto og mappe som `UIDVALIDITY` og seneste færdige UID. Hvis serveren ændrer `UIDVALIDITY`, starter en ny UID-sekvens; den gamle UID må aldrig tolkes som identitet i den nye sekvens. En batchgrænse gælder pr. mappe pr. kørsel.

## 5. Drift og fejl

Log konto-ID, mappenavn, antal fundne og hentede mails, seneste UID og næste kontroltidspunkt. Log sikre fejl uden passwords, OAuth-tokens, IMAP-kommandoer med legitimationsoplysninger eller mailindhold. Én kontos fejl må ikke forhindre forsøg på de andre konti. Brug forbindelses- og læsetimeouts, og lad fejl give en ny prøve ved næste interval frem for at springe en UID over.

Det lokale arkiv og tilstanden er separate data. En flytning til en ny computer skal omfatte begge dele eller en bevidst ny fuld hentning. Programmet må ikke afhænge af en MailFetcher-webservice.

## 6. Det skal kunne bevises

- En test-IMAP-server ser kun `EXAMINE`, `UID SEARCH` og `BODY.PEEK[]` ved hentning. Serverens læst-flag og mappeindhold forbliver uændret.
- En mail med MIME-vedhæftning er byte-for-byte identisk med den hentede EML-fil. En afbrudt literal efterlader ingen færdig fil og flytter ikke fremdriften.
- Gentaget kørsel giver ingen dubletter. Genstart efter en halv batch fortsætter fra sidste færdige UID. Ny `UIDVALIDITY` behandles som en ny sekvens.
- Forkert TLS-certifikat, udløbet OAuth-grant, ugyldig konfiguration og en ulæselig tilstandsfil stopper sikkert med en forståelig fejl. Ingen hemmeligheder står i loggen.
