Wenn die E-Mail zu groß ist
Wie ein selbst gehosteter, verschlüsselter File-Drop mit Gokapi, Cloudflare und OCI Object Storage eine stillschweigend blockierte Bewerbungsunterlage gerettet hat — und zwei Dinge, die mich beim Bauen überrascht haben.
Das Problem: die E-Mail kam einfach nie an
Mein E-Mail-Versand läuft über einen Cloud-Relay-Dienst — praktisch, zuverlässig, aber mit einer Einschränkung, die ich erst schmerzhaft lernen musste: eine harte Obergrenze von 2 MB pro Nachricht, Anhänge inklusive. Über diesem Limit passiert nicht das, was man erwartet (eine klare Fehlermeldung), sondern etwas Schlimmeres: die Verbindung wird beim Versand einfach stillschweigend abgebrochen. Kein Bounce, keine Fehlermeldung — nur eine Mail, die tagelang in der Warteschlange hängt, bis irgendwann eine vage "verzögert"-Warnung kommt.
Das ist mir bei einer wichtigen Bewerbungsunterlage passiert, mit einer echten Deadline dahinter. Kein Server-Problem im klassischen Sinne, aber ein Weckruf: für alles über ein paar Megabyte brauche ich einen anderen Weg.
Die Übersicht, bevor es technisch wird
Kurz gesagt: statt einer Datei im Anhang bekommt der Empfänger nur einen kurzen Link. Die eigentliche Datei liegt verschlüsselt in einem Objekt-Speicher, der Server dazwischen ist für Fremde nicht direkt erreichbar, und der Link läuft nach kurzer Zeit automatisch ab.
Die Optionen — und warum ich keine davon einfach genommen habe
Die naheliegende Lösung wäre ein x-beliebiger Cloud-Speicher-Link gewesen. Für sensible Dokumente wollte ich aber etwas, das drei Eigenschaften gleichzeitig erfüllt:
- Ich kontrolliere die Daten — keine Drittanbieter-Cloud, bei der ich den Zugriffsbedingungen vertrauen muss.
- Verschlüsselt, mehrfach — sowohl auf der Storage-Ebene als auch anwendungsseitig, nicht nur "der Anbieter verspricht Verschlüsselung".
- Kurzer, professioneller Link — der Empfänger (in diesem Fall eine HR-Kontaktperson) soll einfach klicken können, kein Account, kein Schlüsselaustausch.
Die Antwort war Gokapi, ein schlankes Open-Source-Tool für temporäre, ablaufende Download-Links, hinter Cloudflare versteckt, mit Oracle Cloud Infrastructure (OCI) Object Storage im Hintergrund.
Die Architektur in Kurzform
Empfänger ── https ──▶ Cloudflare (TLS-Terminierung,
Rate-Limiting, Cache-Bypass für Downloads)
│ Origin-Pull nur über mTLS —
│ nur Cloudflare selbst darf durch
▼
Origin-Server
┌───────────────────────────────────────────┐
│ nginx — mTLS-Client-Zertifikat prüft, │
│ dass die Anfrage wirklich von Cloudflare │
│ kommt, nicht von irgendwo sonst │
│ Gokapi (Docker) │
│ • nur Metadaten liegen auf dem Server │
│ • Datei-Inhalte → OCI Object Storage, │
│ verschlüsselt (Provider-Key + Gokapis │
│ eigene Anwendungsverschlüsselung) │
└───────────────────────────────────────────┘
Ein paar Design-Entscheidungen, die ich für nachvollziehbar genug halte, um sie zu teilen:
Der Origin-Server ist nicht direkt öffentlich erreichbar — mTLS statt
IP-Filter. Der naheliegende Reflex wäre gewesen: eine Firewall-Regel,
die nur Cloudflares IP-Bereiche durchlässt. Das ist in der Praxis fragiler, als
es klingt — nginx schreibt die "wahre" Client-IP schon vor der
eigentlichen Zugriffsprüfung um, wodurch eine simple allow/deny-Regel
plötzlich die falsche Adresse testet und legitime Anfragen blockiert statt
bösartige. Die robustere Lösung ist ein Client-Zertifikat, das ausschließlich
Cloudflare besitzt (Authenticated Origin Pulls) — der Origin verifiziert das
Zertifikat kryptografisch, unabhängig davon, welche IP gerade anklopft.
Doppelte Verschlüsselung, kein reines "Vertrau dem Anbieter". OCI Object Storage verschlüsselt mit einem eigenen (nicht dem Standard-) Schlüssel, und zusätzlich verschlüsselt Gokapi selbst jede Datei, bevor sie überhaupt hochgeladen wird. Zwei unabhängige Schichten statt einer.
Downloads laufen durch Gokapi, nicht per Direktlink zum Objekt-Speicher. Die naheliegende Abkürzung — den Client direkt zu einer temporären Storage-URL umleiten — spart zwar Bandbreite auf dem eigenen Server, hätte aber zwei Nachteile gehabt: der Empfänger sähe kurz eine hässliche, technische URL statt eines sauberen Links, und die serverseitige "nach X Downloads löschen"-Logik ließe sich leichter umgehen. Also läuft jeder Download durch Gokapi selbst.
Wie ein Download tatsächlich abläuft
Das Diagramm oben zeigt die Stationen, aber nicht, was bei jedem Klick wirklich passiert. Der Reihe nach:
- 1. Hochladen. Ich lade die Datei im Browser bei Gokapi hoch. Gokapi verschlüsselt sie sofort serverseitig, bevor überhaupt ein Byte davon in Richtung Objekt-Speicher geht — der Speicher selbst bekommt nie die unverschlüsselte Datei zu Gesicht.
- 2. Ablegen. Die verschlüsselte Datei landet in OCI Object Storage. Der Server selbst speichert nur ein paar Metadaten: eine kurze, zufällige ID, Ablaufdatum, verbleibende Downloads, optional ein Passwort-Hash. Die Datei selbst liegt nie auf der Festplatte des Servers.
- 3. Link teilen. Der Link, den ich in die E-Mail einfüge, enthält nur diese ID — nicht die Datei und keinen Schlüssel. Ohne einen gültigen, noch nicht abgelaufenen Eintrag in Gokapis eigener Datenbank funktioniert der Link überhaupt nicht.
- 4. Empfänger klickt. Die Anfrage geht zuerst an Cloudflare. Cloudflare holt sich die Antwort vom Origin-Server — aber nur, weil es sich dabei selbst mit seinem eigenen Client-Zertifikat ausweist (das oben erwähnte mTLS). Jede Anfrage, die diesen Umweg über Cloudflare nicht nimmt, wird vom Origin-Server abgelehnt, bevor überhaupt irgendein Inhalt ausgeliefert wird.
- 5. Ausliefern. Gokapi holt die verschlüsselte Datei selbst aus dem Objekt-Speicher und schickt sie an den Browser des Empfängers — kein Umleiten zu einer separaten Speicher-Adresse, alles läuft über denselben, abgesicherten Weg. Erst im Browser des Empfängers wird die Datei entschlüsselt, direkt bevor sie gespeichert wird. Danach zählt Gokapi den Download; ist das Limit erreicht, liefert derselbe Link beim nächsten Versuch nichts mehr aus.
An keiner Stelle dieser Kette gibt es einen Schritt, bei dem die unverschlüsselte Datei irgendwo länger liegen bleibt, als für die jeweilige Verarbeitung nötig ist — und ohne das Cloudflare-Zertifikat kommt niemand überhaupt bis zu Schritt 5.
Was mich beim Bauen überrascht hat
Die Architektur auf dem Papier war der einfache Teil. Ein paar Details haben mich beim tatsächlichen Aufsetzen kalt erwischt — genau die Art von Ding, die in keinem Tutorial steht:
Aus Gewohnheit setze ich auf jedem Origin-Server restriktive
Security-Header, darunter einen, der verhindert, dass die Seite in einem
<iframe> eingebettet werden kann — eine
Standard-Absicherung gegen Clickjacking. Gokapis Download-Client nutzt
aber intern genau so ein Iframe (für einen Service-Worker, der die
Entschlüsselung im Browser übernimmt) — same-origin, technisch harmlos,
aber mein Header hat trotzdem draufgehalten. Der Download hing endlos,
ganz ohne Fehlermeldung am Server. Gefunden erst über die
Browser-Konsole, nicht über irgendein Server-Log. Die Lehre: "restriktiv
by default" ist keine Universallösung — man muss verstehen, was die
eigene Anwendung tatsächlich braucht, bevor man Header pauschal auf die
schärfste Einstellung dreht.
Nach dem Anlegen des neuen DNS-Eintrags schlug die TLS-Verbindung minutenlang fehl — genau das Bild, das man bei einem frisch angelegten, noch propagierenden Zertifikat erwarten würde. Tatsächlich war die Ursache simpler und hätte sich nicht von selbst gelöst: Cloudflares "Universal SSL"-Grundfunktion für automatische Zertifikatsausstellung war schlicht deaktiviert. Ohne sie stellt Cloudflare nie ein Zertifikat aus, egal wie lange man wartet. Die Lehre hier: "wartet noch auf Propagation" ist eine verlockende, aber nicht immer richtige erste Diagnose — lohnt sich, kurz die Grundeinstellungen zu prüfen, bevor man einfach Kaffee holen geht.
Fazit
Der eigentliche Bau — Container aufsetzen, Storage anbinden, Cloudflare konfigurieren — war an einem Nachmittag erledigt. Die genannten Überraschungen haben insgesamt länger gedauert als der Rest zusammen, und keine davon stand in der offiziellen Dokumentation der beteiligten Tools. Das ist letztlich auch der Grund, warum ich das hier aufschreibe: nicht als "so baut man einen File-Drop"-Tutorial, sondern als Sammlung der Stellen, an denen die Realität von der Doku abweicht — und wo genauer hinschauen sich gelohnt hat.
Die ursprüngliche Deadline habe ich am Ende trotzdem verpasst — aber der File-Drop funktioniert jetzt, die Unterlage ist raus, und für das nächste Mal bin ich vorbereitet.
Bei spezifischen Fragen zu Konfiguration, Deployment oder eigenen File-Drop-Setups erreichst du mich über die Kontaktseite.
Verwendete Tools & Projekte
- Gokapi — self-hosted File-Drop mit eingebauter Verschlüsselung und ablaufenden Download-Links (AGPL-3.0)
- Cloudflare — DNS, TLS-Terminierung, Authenticated Origin Pulls (mTLS)
- nginx — Reverse-Proxy und mTLS-Durchsetzung am Origin
- Docker — Container-Betrieb für Gokapi
- Oracle Cloud Infrastructure (OCI) Object Storage — verschlüsselter Objekt-Speicher für die Datei-Inhalte