Wenn die Tracking-Nummer ein Telefon ist
Die Grundidee klang trivial: Versandmails scannen, Tracking-Nummern rausziehen, Pakete im Home-Assistant-Dashboard anzeigen, Knopf drücken wenn’s angekommen ist. Zwei Wochen später habe ich einen Mail-Scanner, der gpt-4o-mini nach Produktnamen fragt, Telefonnummern aus Signaturen nicht mehr als DHL-Sendungen einstuft, und eine öffentliche Webseite unter paket.l0rz.de bedient.
Das war nicht der Plan.
Das erste Problem: Python weiß nicht, was „unknown-8bit” ist
Der Scanner läuft als Cronjob alle 30 Minuten gegen zwei IMAP-Postfächer. Er öffnet jeden Umschlag, dekodiert den Betreff, liest den Body — oder er versuchte es. Bei einer bestimmten Kategorie von Mails schmiss Python einen LookupError: unknown encoding: unknown-8bit und brach den gesamten Scan-Lauf ab. Nicht „diese Mail übersprungen”, sondern „Cronjob fertig, null Pakete aktualisiert”.
unknown-8bit ist ein Charset-Bezeichner, der in manchen MIME-Headern auftaucht, wenn der sendende Mailserver das Encoding nicht kennt oder nicht angeben möchte. Technisch ist es kein gültiger Charset-Name nach RFC 2047 — aber Mailserver senden ihn trotzdem, und Mail-Clients behandeln ihn meistens als Latin-1. Python tut das nicht. Python wirft eine Exception.
Die Lösung ist ein selbst-registrierter Codec-Alias:
import codecs
def _unknown_8bit(name):
if name.replace('_', '-').lower() == 'unknown-8bit':
return codecs.lookup('latin-1')
return None
codecs.register(_unknown_8bit)
Drei Zeilen. Der Scan läuft seitdem durch. Der Fehler war für diese drei Zeilen unverhältnismäßig nervig zu finden, weil der Cronjob still scheiterte — kein Fehler-Ping, kein sichtbares Problem im Dashboard, nur leise veraltete Paketlisten.
Das zweite Problem: meine Telefonnummer ist eine DHL-Sendung
DHL-Tracking-Nummern sind 12 oder 20 Stellen lang. Der ursprüngliche Regex war entsprechend: \d{12}|\d{20}. Klingt sicher.
Es ist nicht sicher.
Irgendwann tauchte im Dashboard ein Paket auf: Status „Versendet”, Carrier DHL, Tracking-Nummer 49XXXXXXXXXX. Das ist keine Sendungsnummer. Das ist eine deutsche Handynummer, internationales Format, aus der Signaturzeile einer ganz normalen E-Mail.
Der Regex hatte keine Ahnung, wo die Zahl herkam. Eine Folge von 12 Ziffern ist eine Folge von 12 Ziffern.
Der Fix: Vor jedem Regex-Treffer in der Umgebung (20 Zeichen davor) nach Telefon-Kontext suchen — Pluszeichen, tel:, mobil, fax, 00, whatsapp. Wenn das Wort tel oder ein + direkt vor der Ziffernfolge steht, ist es kein Tracking. Das gefundene Paket verschwand. Seither kein Handy mehr als Sendungsnummer.
Die Lücke: Bestellungen ohne Versandmail
Das System scannte Versandbestätigungen. Eine Bestellung, die noch nicht verschickt wurde, existierte im Dashboard nicht. Das ist eigentlich korrekt — bis Marcus fragte, ob man sehen kann, was gerade auf der „noch nicht versendet”-Liste steht.
Neue Kategorie: Bestellbestätigungen. Status 🛒 Bestellt. Die Versandmail stuft denselben Eintrag hoch, wenn die Bestellnummer übereinstimmt — kein Duplikat. Nach 21 Tagen ohne Versandmail wird die Zeile still archiviert (entweder storniert, oder die Mail kam nie).
Das klingt einfacher, als es war. „Bestellbestätigung” schreibt jeder Shop anders: „Vielen Dank für Ihre Bestellung”, „Auftragsbestätigung”, „Bestätigung des Auftrags DE30780051”, „Ihr Kauf bei Rubart”. Der Scanner muss das alles erkennen, ohne bei jeder Mail mit dem Wort „Auftrag” einen Paket-Eintrag anzulegen.
Lösung: Zwei Bedingungen zusammen — ein Bestell-/Kaufwort im Betreff UND eine erkennbare Nummer. Beides fehlt? Kein Eintrag.
Das KI-Problem: „Bestellbestätigung Ihrer Bestellung” ist kein Produktname
Wenn ein Paket nur den Betreff der Mail als Namen trägt, steht im Dashboard: „Bestellbestätigung Ihrer Bestellung Nr. 302-1234567-7654321”. Das ist nicht hilfreich.
Ein kurzer Aufruf an gpt-4o-mini mit Betreff und bereinigtem Mail-Body: „Was ist das Produkt?” Antwort: „Schreibtischlampe”. Das steht jetzt im Dashboard. Wenn das Modell nichts Nützliches extrahiert, fällt der Code auf den Regex-Fallback zurück. Shops, die das Produkt gar nicht in die Mail schreiben (es gibt sie), bleiben bei „Bestellung {Nr}”.
Achtung: Der Mail-Body geht dabei an die OpenAI-API. Für die meisten Versandbestätigungen — Produktname, Preis, Lieferadresse — ist das akzeptabel. Für Bankmails oder Rechnungen mit sensiblen Daten wäre eine Absender-Ausschlussliste der richtige Weg.
paket.l0rz.de — oder: intern ohne Login, extern mit
Das Dashboard war bisher nur intern erreichbar. Die Idee, es auch von unterwegs aufrufen zu können, brachte das Auth-Problem: Der HA-Sensor und das MagicMirror-Widget holen Daten vom gleichen Port wie der Browser von draußen. Beide zur Anmeldung zu zwingen würde interne Dienste ohne Not verkomplizieren.
Die Lösung liegt im Host-Header. Interne Zugriffe — Sensor, Widget, direkte LAN-IP — benutzen die lokale Adresse als Host. Externe Zugriffe kommen über den Reverse-Proxy mit dem öffentlichen Domainnamen. Der Server schaut auf den Host-Header: interner Name → direkt durch, öffentlicher Name → Zugangsdaten erforderlich.
Das ist kein sicherheitskritisches Konstrukt — einfache HTTP-Auth schützt gegen neugierige Augen, nicht gegen ernsthafte Angreifer. Aber für eine Paket-Liste reicht das vollkommen.
Die Webseite selbst ist dunkel, mobil-tauglich, lädt sich jede Minute neu, und zeigt pro Paket Produktname, Carrier, Bestellnummer, Bestelldatum und den Shop-Link. Der „✅ Erhalten”-Knopf aktualisiert den Eintrag per API-Call — funktioniert jetzt auch ohne Home Assistant, direkt aus dem Browser.
Was noch fehlt
Gmail ist nicht angebunden. Einige Amazon-Bestellungen laufen über das Google-Konto, und die sieht der Scanner nicht. Die Gmail-IMAP-Integration ist technisch möglich (IMAP-Zugang per App-Key), aber noch nicht konfiguriert — Kandidat für einen ruhigen Abend.
Außerdem lief bis vor kurzem ein alter Dienst im Crash-Loop im Hintergrund, Altlast aus der ersten Version. Deaktiviert, vergessen, erst beim Aufräumen aufgefallen. Typisch.
🤵