Automation

Posteingang sortieren

Acht Mails, sieben Fächer, und der Fehler, durch den eine Gewinnmail mit PDF-Anhang im Rechnungsordner landete — vor genau der Person, deren Arbeit es ist, dort Anhänge zu öffnen.

Anton Brinckmann7 Min. Lesezeit

Stand
22. September 2026
Posteingang sortieren
Teilen

Das Sammelpostfach einer Kanzlei bekommt an einem gewöhnlichen Tag alles durcheinander: eine Verfügung vom Landgericht, eine Mandantenfrage, zwei Rechnungen, drei Newsletter, eine Abwesenheitsnotiz und etwas, das vorgibt, eine Rechnung zu sein. Jemand sieht das durch und verteilt es. Jeden Morgen, seit Jahren.

Das ist die naheliegendste Automatisierung überhaupt, und sie ist zugleich die, bei der am meisten schiefgehen kann — weil eine falsch einsortierte Mail nicht auffällt, sondern in einem Fach liegt, in das niemand mehr schaut. Dieser Artikel baut einen Sortierer für acht Mails, findet dabei den Fehler, der in dieser Bauweise fast zwangsläufig auftritt, und zeigt, warum die Reihenfolge der Regeln wichtiger ist als ihre Anzahl.

Was du danach in der Hand hast

  • ein Skript, das Mails aus einem Ordner oder per IMAP liest und jede in eines von sieben Fächern legt, mit einer Begründung je Mail
  • eine Regeldatei mit den Fächern, den Zielordnern, der eigenen Domäne, den Behördenbereichen und den Lockrufen
  • acht Testmails im echten Mailformat, mit denen die Ausgabe unten entstanden ist
  • einen n8n-Ablauf mit denselben Regeln, der erst verschiebt, wenn ein Schalter umgelegt ist

Voraussetzungen

  • Python 3.12, keine Pakete außerhalb der Standardbibliothek; die Mailverarbeitung, die Muster und der IMAP-Teil kommen mit email, re und imaplib aus
  • ein Sammelpostfach per IMAP mit sieben angelegten Unterordnern; die Namen stehen in der Regeldatei und sind ohne Umlaute, weil IMAP dafür eine eigene Kodierung verlangt
  • n8n, selbst gehostet, falls der Ablauf dort laufen soll; das Skript läuft auch allein, etwa alle zehn Minuten per Cron
  • Ollama mit llama3.2 nur für den Modellschritt am Ende, und der ist, wie sich zeigen wird, verzichtbar
  • Kosten: keine laufenden

Der Aufbau

  1. 01

    Kopfzeilen zuerst

    Auto-Submitted, List-Unsubscribe und Precedence werden vor allem anderen geprüft. Trifft eine davon, ist die Mail sortiert, egal was im Text steht.

  2. 02

    Dann der Absender

    Die eigene Domäne geht nach Intern, bekannte Behördenbereiche nach Gericht und Behörden. Hier gibt es keine Muster, nur Listen.

  3. 03

    Dann der Text, mit Beweislast

    Gerichtliche Aktenzeichen, das eigene Aktenzeichen, Rechnungsbelege. Für das Rechnungsfach reicht ein Merkmal nicht; es braucht zwei, und ein Lockruf im Text verhindert die Einsortierung ganz.

  4. 04

    Der Rest zur Prüfung

    Was keine Regel greift, kommt in einen eigenen Ordner, nicht in den Posteingang zurück. Der Ablauf schreibt die Liste; verschoben wird erst mit Schalter, gelöscht nie.

Das Ergebnis

8 Mails, 7 einsortiert, 1 zur Pruefung

Automatische Antwort
    05_abwesenheit.eml       Automatische Antwort: Rueckfrage zu Az. 2026-0441
                               -> Kopfzeile Auto-Submitted

Gericht und Behoerden
    03_gericht.eml           LG Duesseldorf, 4 O 221/26 - Verfuegung
                               -> Absender poststelle@lg-duesseldorf.nrw.de, Az 4 O 221/26

Intern
    07_intern.eml            Re: Besprechung Donnerstag
                               -> Absender aus dem eigenen Haus, Az 2026-0441

Mandat
    01_mandant_frage.eml     Rueckfrage zu Az. 2026-0441
                               -> Aktenzeichen 2026-0441 im Text, Az 2026-0441

Rechnungen
    02_rechnung.eml          Rechnung 2026/8812
                               -> Rechnungsnummer + Anhang heisst nach Rechnung

Werbung und Newsletter
    04_newsletter.eml        Neues zum Aktenzeichen-Management in Kanzleien
                               -> Kopfzeile List-Unsubscribe, Precedence: bulk
    06_werbung.eml           Ihre Kanzlei verliert jeden Monat 40 Stunden
                               -> Kopfzeile List-Unsubscribe

Zur Pruefung von Hand

    08_spam.eml              Sie haben gewonnen! Rechnung anbei
                               (Wort Rechnung, aber Lockruf im Text)

  nichts verschoben (--verschieben fehlt)

Hinter jeder Einordnung steht, woran es lag. Das ist kein Schmuck: Wenn nach drei Wochen jemand sagt, die Sortierung stimme nicht, ist diese Zeile der Unterschied zwischen „wir sehen nach“ und „wir bauen es neu“.

Der Kopf lügt seltener als der Text

Die wichtigste Entscheidung in diesem Sortierer ist die Reihenfolge der Regeln, und die erste Regel lautet: Was in den Kopfzeilen der Mail steht, schlägt alles, was im Text steht.

Der Newsletter im Beispiel heißt „Neues zum Aktenzeichen-Management in Kanzleien“. Er enthält die Wörter Aktenzeichen, Fristen und Mandat. Jede textbasierte Regel würde ihn für eine Mandatssache halten. Seine Kopfzeilen sagen aber unmissverständlich, was er ist: List-Unsubscribe und Precedence bulk. Beides setzt kein Mensch von Hand, beides setzen Versandsysteme.

def ist_massenmail(m):
    """Kopfzeilen, die eine Massensendung verraten. Der Text luegt, der Kopf selten."""
    gruende = []
    if m.get("List-Unsubscribe"):
        gruende.append("List-Unsubscribe")
    if (m.get("Precedence") or "").lower() in ("bulk", "list", "junk"):
        gruende.append("Precedence: " + m.get("Precedence"))
    return gruende

Dasselbe gilt für Abwesenheitsnotizen. Die Mail im Beispiel trägt den Betreff „Automatische Antwort: Rückfrage zu Az. 2026-0441“ — sie enthält also ein gültiges eigenes Aktenzeichen und würde im Mandatsfach landen, wo sie nichts zu suchen hat. Die Kopfzeile Auto-Submitted verrät sie in einem Schritt. Diese Kopfzeile steht in einer Norm, und fast alle Mailsysteme setzen sie.

Die praktische Regel daraus: Kopfzeilen zuerst, Absender danach, Textinhalt zuletzt. Wer mit dem Text anfängt, baut einen Sortierer, der sich von jeder Werbemail hereinlegen lässt, die die richtigen Wörter benutzt — und Werbung an Kanzleien benutzt die richtigen Wörter.

Der Fehler, der eine Phishing-Mail in die Buchhaltung befördert

Die erste Fassung hatte für Rechnungen eine Regel, die vernünftig klingt: Wenn das Wort „Rechnung“ vorkommt und ein Anhang dabei ist, ist es eine Rechnung.

def rechnungsbelege(alles, anhaenge):
    """Ein Wort reicht nicht. Gezaehlt wird, was eine Rechnung ausmacht."""
    belege = []
    if RECHNUNGSNUMMER.search(alles):
        belege.append("Rechnungsnummer")
    if BETRAG.search(alles):
        belege.append("Betrag mit Waehrung")
    if any(ANHANG_RECHNUNG.search(a or "") for a in anhaenge):
        belege.append("Anhang heisst nach Rechnung")
    return belege

# ... in sortiere():
if WORT_RECHNUNG.search(alles) or any(ANHANG_RECHNUNG.search(a) for a in anhaenge):
    belege = rechnungsbelege(alles, anhaenge)
    if regeln["_lockruf"].search(alles):
        return None, "Wort Rechnung, aber Lockruf im Text"
    if len(belege) >= regeln["rechnung_mindestens_belege"]:
        return "Rechnungen", " + ".join(belege)
    return None, "Wort Rechnung, aber nur %d Beleg" % len(belege)

Die Mindestzahl der Belege und die Lockrufe stehen in der Regeldatei, nicht im Code. Wer nach einem Monat merkt, dass ein Lieferant seine Rechnungen ohne Nummer im Text schickt, ändert eine Zahl, nicht ein Programm.

Zwei Sorten Aktenzeichen

Ein Detail, das beim ersten Bauen gern untergeht: In einer Kanzlei gibt es zwei Arten von Aktenzeichen, und sie sehen völlig verschieden aus. Das eigene hat eine Hausform, etwa 2026-0441. Das des Gerichts folgt einer anderen Systematik, etwa 4 O 221/26.

AKTENZEICHEN = re.compile(r"\b(\d{1,3}\s?[A-Z]{1,3}\s?\d{1,5}/\d{2})\b")   # 4 O 221/26
EIGENES_AZ   = re.compile(r"\b(20\d\d-\d{3,5})\b")                            # 2026-0441

Beide zu erkennen lohnt sich, weil das gerichtliche Aktenzeichen in der Mail vom Gericht steht und das eigene in der Mail vom Mandanten. Wer nur eins von beiden sucht, sortiert die halbe Post nicht zu. Und wer die Zuordnung zwischen beiden pflegt, hat den nützlichsten Datensatz der ganzen Kanzlei — aber das ist ein anderer Artikel.

Ein Modell für den Rest, und warum es aus bleibt

Die naheliegende Ergänzung: Was keine Regel greift, bekommt ein lokales Modell vorgelegt, das ein Fach vorschlägt. Wir haben das eingebaut, als Schalter, und am 18. September 2026 mit llama3.2 über die Prüfliste laufen lassen. Für die Gewinnmail kam das hier zurück:

Zur Pruefung von Hand

    08_spam.eml              Sie haben gewonnen! Rechnung anbei
                               (Wort Rechnung, aber Lockruf im Text)
                               Modell: Antwort nicht lesbar: '{"Warnung": "Betrugsmail", "Inhalt": "Die E-Mail enthält eine Falle, um persönli'

Das Modell hat die Mail erkannt, aber die Aufgabe nicht erledigt: Statt des verlangten Objekts mit Fach und Grund schreibt es eine Warnung vor Betrug, mit und ohne Beispiele im Prompt, zweimal gleich. Für eine harmlose Anfrage ohne Aktenzeichen, die wir zusätzlich hineingegeben haben, schlug es das Fach „Antwort auf einen Anfrage“ vor, das es nicht gibt. In beiden Fällen bleibt die Mail, wo sie war: in der Prüfliste. Der Schritt ist im Paket, ist standardmäßig aus, und wir würden ihn erst mit einem größeren Modell noch einmal ansehen. Die Kopfzeilen haben in diesem Lauf mehr entschieden als das Modell.

Wie das in n8n aussieht

Der Ablauf lässt sich in n8n weitgehend ohne eigenen Code bauen, und für die meisten Häuser ist das der bessere Weg, weil ihn mehr Leute lesen können. Der Export im Paket hat zehn Knoten:

  • Ein IMAP-Trigger, der neue Mails im Sammelpostfach abholt und ungelesen lässt.
  • Ein Set-Knoten „Einstellungen“ mit dem Schalter VERSCHIEBEN, der eigenen Domäne und den Behördenbereichen. VERSCHIEBEN steht auf false.
  • Ein Code-Knoten, der die Kopfzeilen in flache Felder bringt, dann ein Switch-Knoten auf Auto-Submitted, List-Unsubscribe und Precedence. Diese drei Prüfungen zuerst, in dieser Reihenfolge.
  • Ein Switch-Knoten auf den Absender: eigene Domäne, Behördenbereiche.
  • Ein Code-Knoten für die Mustersuche nach Aktenzeichen und Rechnungsbelegen — der einzige Teil, für den sich Code lohnt, weil reguläre Ausdrücke in Klickoberflächen unlesbar werden. Die Muster sind dieselben wie im Skript.
  • Ein IF-Knoten auf den Schalter. Solange er auf false steht, hängt jede Mail mit Fach und Grund an eine Vorschlagsliste. Erst danach ruft ein Execute-Command-Knoten das Skript auf, das die Mail per IMAP in den Zielordner verschiebt; n8n selbst hat keinen allgemeinen IMAP-Knoten, der verschiebt.

Alles, was keine Regel greift, geht in einen Ordner „Prüfen“ und bleibt nicht im Posteingang liegen. Das ist die einzige Stelle, an der wir widersprechen würden, wenn jemand es anders baut. Unklare Mails im Posteingang zu lassen, klingt vorsichtig, führt aber dazu, dass niemand mehr unterscheiden kann, was noch nicht sortiert wurde und was nicht sortierbar war. Ein eigener Ordner „Prüfen“ macht aus dem Rest eine Aufgabe.

Was wir nicht automatisieren würden

  • Löschen. Nichts an diesem Ablauf löscht etwas, auch nicht offensichtlichen Müll. Verschieben genügt, und ein Ordner für Werbung ist billiger als eine einzige gelöschte Mandantenmail.
  • Antworten. Eine automatische Empfangsbestätigung wirkt harmlos und ist es nicht, sobald sie auf eine Mail antwortet, die falsch einsortiert wurde.
  • Fristen ableiten. Dass in einer Mail vom Gericht ein Termin steht, heißt nicht, dass dieser Ablauf ihn notieren darf. Der Fristenkalender bekommt Vorschläge, keine Einträge.
  • Anhänge öffnen oder weiterverarbeiten, bevor die Einordnung belegt ist. Genau dieser Fehler steht oben.

Zum Nachbauen

Das Paket zu diesem Beitrag enthält das Skript, die Regeldatei, die acht Testmails und den n8n-Export. Entpacken, dann das Skript über den Testordner laufen lassen; die Ausgabe oben ist genau das, was dabei herauskommt:

  • sortiere_posteingang.py: das Skript aus diesem Beitrag. Liest .eml-Dateien aus einem Ordner oder mit --imap ungelesene Mails aus dem Postfach. Verschiebt nur mit --verschieben
  • regeln.json: die sieben Fächer mit Zielordnern, eigene Domäne, Behördenbereiche, Lockrufe, Mindestzahl der Belege
  • testdaten/: die acht Mails als .eml mit den Kopfzeilen, die die jeweiligen Systeme setzen; die Rechnung und die Gewinnmail mit PDF-Anhang
  • prompts/faecher.md: die Anweisung für den Modellschritt, der mit --modell zugeschaltet wird
  • n8n/posteingang.json: der Ablauf mit den beiden Switch-Knoten, dem Muster-Knoten und dem Schalter VERSCHIEBEN
  • erzeuge_testdaten.py: erzeugt die acht Mails neu, falls du eigene Fälle ergänzen willst

Posteingang sortieren: Sortierskript, sieben Fächer, acht Testmails und n8n-Ablauf

ZIP · 21,9 kB

Das Sortierskript mit Kopfzeilen-Regeln und Beweislast für Rechnungen, die Fächer als JSON, die acht Testmails als .eml, der Prompt für den Rest und der n8n-Export mit Verschiebe-Schalter.

Herunterladen

Stand dieses Artikels

Die acht Mails sind selbst erzeugte Testdateien im echten Mailformat, mit den Kopfzeilen, die die beschriebenen Systeme tatsächlich setzen — keine echten Mails aus einem Postfach. Beide abgedruckten Läufe sind echt; der Lauf oben stammt vom 18. September 2026 mit Python 3.12.2, der Modellschritt mit Ollama 0.32.15 und llama3.2 (3B). Der IMAP-Teil des Skripts und der n8n-Export sind nicht gegen ein laufendes Postfach oder ein laufendes n8n geprüft; geprüft ist die Sortierung über die .eml-Dateien.

Der beschriebene Fehler ist im ersten Entwurf tatsächlich aufgetreten. Dass die Gewinnmail ausgerechnet im Rechnungsfach landete, war nicht so geplant: Sie war als offensichtlicher Fall für die Prüfliste gedacht und hat stattdessen gezeigt, wie leicht sich eine schwache Regel überlisten lässt, ohne dass jemand es darauf angelegt hätte.

Häufige Fragen

Wie verhindert man, dass eine Regel zu leicht greift?

Über Beweislast statt Einzelmerkmal: Für das Rechnungsfach werden mindestens zwei Belege verlangt — Rechnungsnummer, Betrag mit Währung, Anhangsname. Was weniger vorweisen kann, kommt in die Prüfliste statt in ein Fach. Und eine kurze Liste von Lockrufen verhindert die Einsortierung unabhängig davon.

Was ist ein gutes Zeichen für eine schlechte Sortierung?

Eine leere Prüfliste. Wenn ein Sortierer bei acht ungleichartigen Mails alle acht zuordnet, greifen die Regeln zu breit. Ein gewisser Anteil unentschiedener Fälle ist kein Mangel, sondern der Beleg dafür, dass überhaupt unterschieden wird.

Sollte der Ablauf offensichtlichen Müll löschen?

Nein. Verschieben genügt in jedem Fall. Ein Ordner voller Werbung kostet nichts; eine einzige fälschlich gelöschte Mandantenmail kostet sehr viel. Dasselbe gilt für automatische Antworten — sie werden gefährlich in dem Moment, in dem sie auf eine falsch einsortierte Mail antworten.

  • Belege an DATEV übergeben

    Belege an DATEV übergeben

    Der Zugang ist niedriger, als sein Ruf vermuten lässt — eine Beraternummer braucht es nicht, die Sandbox kommt mit dem Abonnement. Und elf Stunden Token-Gültigkeit entscheiden über den ganzen Aufbau.

  • Ein Agent, der eure Ablage kennt

    Ein Agent, der eure Ablage kennt

    Das meiste daran ist Suche, nicht Modell. Eine Volltextsuche über die eigene Ablage mit Fundstelle und Begründung — und der Satz, der schlimmer ist als eine leere Trefferliste.

  • Fristen aus einem Vertragsstapel

    Fristen aus einem Vertragsstapel

    Ein Skript liest sechs Vertragsklauseln und rechnet zurück, bis wann eine Kündigung zugehen müsste. Drei Verträge rechnet es, drei nicht — und die drei, die es liegen lässt, sind der interessantere Teil.

© 2026 ernsthaft.ai, ein Angebot der abi group GmbH

Alle genannten Produktnamen, Logos und Marken sind Eigentum der jeweiligen Inhaber. Ihre Nennung bedeutet keine Empfehlung oder Verbindung zu ernsthaft.ai oder der abi group GmbH.

Build v1.10.0+e670d45