Rolle & Umfang
Ein E-Commerce-Unternehmen mit eigenem ERP (NetSuite) versandte über ein SaaS-Versandtool — mit den entsprechenden Kosten und der begrenzten Kontrolle über Carrier-Verträge. CarrierAPI ist der selbstgehostete Ersatz: eine API, die Labels erzeugt, Tarife vergleicht, Sendungen verfolgt und Batches über mehrere Versanddienstleister verarbeitet, mit denselben Anschlusspunkten wie das vorherige Tool, sodass der Umstieg aus NetSuite ein Drop-in-Ersatz sein konnte statt eines Neubaus.
- Eine Adapter-Architektur je Versanddienstleister (PostNL, DHL Express und UPS als Kernset; DHL Parcel, FedEx, GLS und SendCloud selbst als zusätzliche Adapter), sodass ein neuer Dienstleister keine Änderung am restlichen System erfordert.
- Drop-in-Ersatz für das vorherige SaaS-Tool innerhalb von NetSuite: identische Custom-Field-Namen, minimale Änderungen an der bestehenden SuiteScript-Integration.
- Direkte Anbindung an Item-Fulfillment-Datensätze in NetSuite, dazu Synchronisation mit Shopify und eine Amazon-Anbindung neben den Versanddienstleistern.
- Zwei Wege, Carrier-Zugangsdaten zu verwalten: über die UI (datenbankgestützt, bevorzugt) oder über Umgebungsvariablen (Legacy-Pfad für bestehende Deployments).
Wirkung
- Versandkosten und Carrier-Verträge wieder in eigener Hand, statt an Tarife und Bedingungen einer SaaS-Zwischenschicht gebunden zu sein.
- Adresserkennung für eine Klasse von Adressen repariert, die zuvor falsch geparst wurde (die Hausnummern-Extraktion nahm versehentlich die letzte Zahl statt der ersten nach dem Straßennamen — falsch etwa bei Ferienpark-Adressen mit Hausnummer und zusätzlicher Chalet-Nummer).
- Der Rechnungsabgleich mit UPS funktionierte weiter, nachdem UPS zweimal das Exportformat seiner Rechnungen geändert hatte, weil die Spaltenerkennung auf Inhalt statt auf feste Positionen setzt und gegen den Gesamtbetrag geprüft wird, den die Rechnung selbst ausweist.
- Ein Session-Leck geschlossen, bei dem gelöschte Nutzer bis zu 24 Stunden weiter Zugriff hatten, weil nur Signatur und Ablaufdatum ihres Session-Tokens geprüft wurden — nicht, ob das Konto noch existiert.
Architektur
Jeder Versanddienstleister ist ein Adapter hinter einer gemeinsamen Schnittstelle (Sendungen anlegen und stornieren, Tarife abfragen, Tracking, verfügbare Services) — ein neuer Dienstleister berührt den Rest des Systems nicht. Das Backend ist FastAPI (Python, async) mit PostgreSQL als primärer Datenbank und einem Redis-basierten Hintergrund-Worker (arq) für die Batch-Verarbeitung von Labels, sodass das Erzeugen hunderter Labels die API nicht blockiert. Die Labels selbst liegen in MinIO (S3-kompatibel), damit die Speicherung nicht an einen Cloud-Anbieter gebunden ist. Das Frontend ist ein Next.js/React-Dashboard zur Verwaltung von Sendungen, Carrier-Einstellungen und Zugangsdaten. Das Ganze läuft selbstgehostet über Docker Compose hinter Traefik, ausgerollt mit Coolify.
Modellwahl
Keine KI/LLM — das ist bewusst ein deterministisches System: Versandlabels, Tarife und Tracking müssen vorhersagbar und prüfbar sein, nicht generiert. Die zentrale Entscheidung war hier selbstgehostet versus SaaS: Ein SaaS-Versandtool ist schneller aufgesetzt, eine selbstgehostete Plattform gibt aber Kontrolle über Carrier-Verträge, keine Gebühr pro Label oder Sendung an eine Zwischenschicht und die Möglichkeit, Funktionalität (etwa die direkte NetSuite-Anbindung) passgenau zu bauen, statt darauf zu warten, was ein SaaS-Anbieter unterstützt oder eben nicht.
Kosten & Latenz
- Selbstgehostete Infrastruktur (Docker Compose, PostgreSQL, Redis, MinIO) statt einer laufenden SaaS-Gebühr pro Sendung.
- Die Batch-Label-Erzeugung läuft asynchron über eine Redis-Queue (arq), sodass das gleichzeitige Erzeugen hunderter Labels die API reaktionsfähig hält, statt den Anfragenden warten zu lassen.
- Der Rechnungsabgleich wurde gegen echte UPS-Rechnungen geprüft: darunter eine Rechnung mit 1.275 Zeilen über 4.855,72 € und ein Export mit 2.275 Zeilen, beide nach dem Fix an der Spaltenerkennung korrekt verarbeitet.
Lessons learned
Das Parsen niederländischer Adressen ging bei einer bestimmten Klasse von Adressen schief (Ferienparks mit Hausnummer und separater Chalet-Nummer, etwa "Hessenweg 83 41"), weil der Parser die letzte Zahl in der Straßenzeile als Hausnummer las statt der ersten nach dem Straßennamen. Gelöst mit einem gemeinsamen Parser, verifiziert an einem echten Fall. Der UPS-Rechnungsimport brach zweimal, weil UPS das Exportformat seiner Rechnungen änderte (Spaltenzahl und -reihenfolge verschoben sich); der Ansatz mit festen Spaltenpositionen wurde durch inhaltsbasierte Spaltenerkennung ersetzt, deren Ergebnis gegen den von der Rechnung selbst ausgewiesenen Gesamtbetrag geprüft wird. Warnungen der Adressvalidierung blockierten anfangs das Anlegen einer Sendung vollständig; das wurde darauf zurückgenommen, nur den automatischen Label-Kauf zu blockieren, sodass eine markierte Sendung sichtbar und wiederholbar bleibt, statt festzuhängen. Und eine Session-Schwachstelle: Gelöschte Nutzer behielten bis zu 24 Stunden Zugriff, weil Tokens nur auf Signatur und Ablauf geprüft wurden, nicht auf die Existenz des Kontos — behoben, indem bei jedem Request verifiziert wird, dass das Konto noch existiert.