Files
archivmail/features/PROJ-12-export.md
T
sysops bb963a796f security: Zufallspasswörter beim Erststart, kryptographisch sichere JTI-Generierung
- seedDefaultUsers: generiert kryptographisch zufällige Passwörter (crypto/rand)
  statt hartkodiertes "archivmailrockz" — Passwörter werden einmalig im Terminal
  angezeigt und können danach nicht wiederhergestellt werden
- generateJTI: verwendet crypto/rand (16 Byte, hex) statt time.UnixNano XOR deadbeef

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 01:19:24 +01:00

6.7 KiB
Raw Blame History

PROJ-12: E-Mail-Export (EML / PDF)

Status: Deployed

Created: 2026-03-12 Last Updated: 2026-03-17

Dependencies

  • Requires: PROJ-1 (Authentifizierung)
  • Requires: PROJ-6 (Volltext-Suche) Export aus Suchergebnissen
  • Requires: PROJ-7 (E-Mail-Ansicht)
  • Requires: PROJ-11 (Audit-Log) Export-Aktionen werden geloggt

User Stories

  • Als Nutzer möchte ich eine einzelne E-Mail als EML-Datei exportieren, damit ich sie in einem E-Mail-Client öffnen kann.
  • Als Nutzer möchte ich eine E-Mail als PDF drucken/exportieren, damit ich sie für Behörden oder Verträge verwenden kann.
  • Als Admin möchte ich mehrere E-Mails als ZIP-Archiv exportieren, damit ich bei einer Anfrage mehrere Mails auf einmal liefern kann.
  • Als System möchte ich jeden Export im Audit-Log erfassen.

Acceptance Criteria

  • Einzelexport EML: Original-MIME-Inhalt wird unverändert heruntergeladen
  • Einzelexport PDF: E-Mail-Header + Body als gut lesbares PDF gerendert, Anhänge als separate Dateien erwähnt
  • Massenexport: Auswahl mehrerer E-Mails (Checkbox in Suchergebnissen), ZIP-Download
  • ZIP enthält: EML-Dateien + optionale Anhänge + Manifest (CSV mit Metadaten)
  • Massenexport-Limit konfigurierbar (Standard: max. 500 E-Mails pro Export)
  • Jeder Export wird im Audit-Log erfasst (Nutzer, Anzahl E-Mails, Format)
  • Zugriffsschutz: Nutzer kann nur eigene E-Mails exportieren

Edge Cases

  • Export von 500 E-Mails mit großen Anhängen → Streaming-ZIP, kein Speicher-Overflow
  • PDF-Rendering von komplexem HTML → graceful Fallback auf Plain-Text-PDF
  • Nutzer wählt E-Mails aus, auf die er keinen Zugriff hat → diese werden aus Export-Liste entfernt

Technical Requirements

  • ZIP-Erstellung als Stream (nicht komplett in Memory)
  • PDF-Generierung serverseitig (z.B. wkhtmltopdf oder Go-PDF-Bibliothek)

Tech Design (Solution Architect)

Komponentenstruktur

Next.js Frontend:

Suchergebnisse (PROJ-6, Erweiterung)
├── Checkbox pro Treffer (Multi-Select)
├── [Exportieren] Button (aktiv wenn ≥1 ausgewählt)
└── Export-Dialog
    ├── Format: EML | PDF
    ├── Anhänge einschließen: ja / nein
    └── [Download starten] → POST /api/export/zip (Streaming)

E-Mail-Ansicht (PROJ-7, Erweiterung)
├── [Als EML herunterladen] Button  → GET /api/export/eml/{id}
└── [Als PDF exportieren] Button    → GET /api/export/pdf/{id}

Go Backend:

Export-Handler
├── GET /api/export/eml/{id}        ← Einzelexport EML
│   └── Original-.m-Datei lesen, AES-256-GCM entschlüsseln, direkt streamen
│
├── GET /api/export/pdf/{id}        ← Einzelexport PDF
│   ├── .m-Datei lesen + entschlüsseln
│   ├── Header + Body extrahieren
│   └── → PDF-Bibliothek → PDF streamen
│       Fallback: Plain-Text-PDF wenn HTML zu komplex
│
└── POST /api/export/zip            ← Massenexport
    ├── Body: { ids: [...], format: "eml"|"pdf", attachments: bool }
    ├── Zugriffscheck: Nutzer darf nur eigene Mails exportieren
    ├── Max-Limit prüfen (konfigurierbar, Standard: 500)
    ├── Streaming-ZIP (archive/zip Writer → ResponseWriter direkt)
    │   ├── Pro Mail: <message_id>.eml oder <message_id>.pdf
    │   ├── Anhänge: attachments/<hash>/<filename> (wenn aktiviert)
    │   └── manifest.csv (Message-ID, From, To, Subject, Date, Dateiname)
    └── → Audit-Log: export_start + export_done (Anzahl, Format)

Export-Fluss (Massenexport)

POST /api/export/zip
        │
        ├── Zugriffsfilter: IDs auf user-eigene Mails beschränken
        │
        ├── Audit-Log: export_start
        │
        ├── ZIP-Stream öffnen (Content-Type: application/zip)
        │
        └── Für jede Mail:
              .m-Datei lesen → AES-256-GCM entschlüsseln
              → Zu ZIP hinzufügen (kein vollständiges RAM-Buffering)
              ↓
            manifest.csv Zeile anhängen
              ↓
            ZIP schließen → Audit-Log: export_done

Technische Entscheidungen

Entscheidung Begründung
Streaming-ZIP 500 Mails mit Anhängen können mehrere GB sein — kein RAM-Overhead
Serverseitiges PDF Browser-Print ist nicht reproduzierbar; serverseitiges PDF ist auditierbar und einheitlich
Plain-Text-Fallback für PDF Komplexes HTML kann PDF-Renderer zum Absturz bringen — graceful degradation
Zugriffscheck im Export-Handler Serverseitige Filterung verhindert Datenlecks durch manipulierte IDs
manifest.csv im ZIP Nachvollziehbarkeit bei Behördenanfragen ohne jede EML einzeln öffnen zu müssen
Audit-Log für jeden Export Compliance-Anforderung — wer hat wann was exportiert

Abhängigkeiten

Paket Zweck
archive/zip (Stdlib) Streaming-ZIP-Erstellung ohne externe Abhängigkeit
github.com/SebastiaanKlippert/go-wkhtmltopdf PDF-Generierung aus HTML (serverseitig)

Implementation Notes (2026-03-14)

What was built

Go Backend — internal/api/export.go (new file):

  • GET /api/export/pdf/{id} — generates a stdlib-only PDF (no external deps) using raw PDF 1.4 syntax with Helvetica core font. Renders header fields, plain-text body (with HTML-strip fallback), and attachment list. toLatinSafe() converts umlauts for Latin-1 compatibility.
  • POST /api/export/zip — streaming ZIP via archive/zip stdlib. Accepts {"ids":[...], "attachments": true}, max 500 IDs. Adds {id[:16]}.eml, optional attachments/{id[:8]}/{filename} entries, and manifest.csv (CSV with filename/message_id/from/to/subject/date). Audit-logged as export: zip: N mails.
  • Both handlers use requireMailAccess middleware (blocks admin role). RoleUser is filtered to own mails via mailBelongsToUser; RoleAuditor can export all.

Deviation from spec: PDF generation uses a hand-rolled stdlib PDF writer instead of go-wkhtmltopdf or go-pdf/fpdf — avoids adding an external dependency that would require go get on the server.

Routes added to internal/api/server.go:

  • GET /api/export/pdf/{id}
  • POST /api/export/zip

Frontend src/lib/api.ts:

  • Added exportMailPDF(id) and exportMailsZIP(ids, attachments) export functions.

Frontend src/app/mail/[id]/page.tsx:

  • Added "Als PDF exportieren" button next to "Als .eml herunterladen".

Frontend src/app/search/page.tsx:

  • Added per-row Checkbox column + select-all header checkbox.
  • Export toolbar appears when ≥1 mail is selected.
  • ZIP export dialog with attachments toggle (Switch component).
  • Selection cleared when search results change.

QA Test Results

To be added by /qa

Deployment

To be added by /deploy