Automation
Eingangsprüfung: ist das überhaupt eine E-Rechnung?
Bevor ein Ablauf Beträge ausliest, muss jemand entscheiden, ob die Datei überhaupt zählt. Wir bauen den Schritt: Daten finden, Profil lesen, Urteil ausgeben — mit vier Testdateien zum Nachvollziehen.
- Stand

Seit die Empfangspflicht gilt, liegen im Postfach Dateien, die alle eine Rechnung sein wollen. Eine XML, die niemand öffnen kann. Eine PDF, die vielleicht Daten enthält und vielleicht nicht. Und eine, auf der ZUGFeRD steht, die trotzdem keine E-Rechnung ist. Bevor irgendein Ablauf Beträge ausliest und in die Buchhaltung schreibt, muss jemand diese Frage beantworten. Wir bauen den Schritt, der sie beantwortet.
Was du danach in der Hand hast:
- ein Skript, das eine eingegangene Datei einordnet und ein lesbares Urteil ausgibt
- vier Testdateien, an denen du die vier Fälle siehst
- dieselbe Ausgabe als JSON, die sich in n8n weiterverarbeiten lässt
Voraussetzungen: Python 3.11 oder neuer und zwei Pakete, pypdf für die Anhänge und lxml für die XML. Wer die Testdateien selbst erzeugen will, braucht zusätzlich reportlab. Keine Lizenz, kein Konto, kein Server.
Was im Postfach ankommt
Vier Sorten Datei, und der Ablauf muss sie auseinanderhalten:
- Eine reine XML-Datei. Normalfall bei Rechnungen an Behörden, zunehmend auch von Firmen.
- Eine PDF mit eingebetteter XML. Von außen sieht sie aus wie jede andere PDF, die Daten liegen als Anhang darin.
- Eine gewöhnliche PDF. Seit 2025 keine E-Rechnung mehr, sondern eine sonstige Rechnung.
- Eine Datei wie die zweite, deren eingebettete Daten aber ein Profil tragen, das die Pflicht nicht erfüllt. Das ist der unangenehme Fall, weil von außen nichts daran auffällt.
Der Aufbau, und was wir weglassen
- 01
Die strukturierten Daten finden
Ist die Datei selbst XML, sind wir fertig. Ist es eine PDF, sehen wir ihre Anhänge durch und suchen nach den drei Namen, unter denen die Daten üblicherweise liegen. Findet sich keiner, ist es keine E-Rechnung, und der Ablauf endet hier.
- 02
Das Profil lesen
Jede Rechnung trägt eine Kennung, die sagt, nach welchem Profil sie gebaut ist. Sie steht immer an derselben Stelle. Aus ihr ergibt sich, ob die Datei die Pflicht erfüllt.
- 03
Nachzählen und fünf Felder holen
Rechnungsnummer, Datum, Verkäufer, Gesamtbetrag, Währung. Dazu die Anzahl der Positionen, weil die der eigentliche Unterschied zwischen den Profilen ist.
- 04
Ein Urteil ausgeben
Angenommen, zurück an den Absender oder abgelehnt. Einmal für Menschen lesbar, einmal als JSON für den nächsten Schritt.
Weggelassen haben wir die vollständige Prüfung gegen die Norm. Die EN 16931 bringt mehrere hundert Geschäftsregeln mit, und die durchzurechnen ist Arbeit für ein eigenes Werkzeug. Für die Frage, die dieser Schritt beantworten soll, braucht es sie nicht.
Die Daten finden, auch wenn sie im PDF stecken
Der erste Schritt ist unspektakulär und gleichzeitig der, an dem die meisten selbstgebauten Abläufe scheitern: Eine ZUGFeRD-Rechnung ist eine PDF, und wer nur auf die Dateiendung sieht, hält sie für ein Bild.
ANHANGSNAMEN = ("factur-x.xml", "zugferd-invoice.xml", "xrechnung.xml")
def hole_xml(pfad):
"""Gibt (xml_bytes, herkunft) zurueck oder (None, grund)."""
roh = open(pfad, "rb").read()
if roh.lstrip()[:1] == b"<":
return roh, "XML-Datei"
if roh[:4] != b"%PDF":
return None, "weder XML noch PDF"
from pypdf import PdfReader
anhaenge = PdfReader(pfad).attachments
for name in anhaenge:
if name in ANHANGSNAMEN:
return anhaenge[name][0], "eingebettet in PDF als %s" % name
if anhaenge:
return None, "PDF mit Anhaengen, aber keiner davon ist eine Rechnung"
return None, "PDF ohne eingebettete Daten"Die drei Namen sind nicht geraten, sie stehen in den Spezifikationen. Ein Absender, der einen vierten wählt, baut eine Datei, die auch anderswo nicht erkannt wird.
Das Profil lesen, und was es entscheidet
Die Profilkennung steht in jeder Rechnung an derselben Stelle. Die Zuordnung, welche davon reicht, ist die ganze fachliche Substanz dieses Ablaufs:
PROFILE = {
"urn:factur-x.eu:1p0:minimum": ("MINIMUM", False),
"urn:zugferd.de:2p0:minimum": ("MINIMUM", False),
"urn:factur-x.eu:1p0:basicwl": ("BASIC-WL", False),
"urn:zugferd.de:2p0:basicwl": ("BASIC-WL", False),
"urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic": ("BASIC", True),
"urn:cen.eu:en16931:2017": ("EN 16931", True),
"urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended": ("EXTENDED", True),
"urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:cius:xrechnung:3.0": ("XRECHNUNG", True),
}Steht in der Datei eine Kennung, die hier nicht vorkommt, lehnt der Ablauf sie nicht ab. Er markiert sie zur Prüfung von Hand. Eine Liste, die alles Unbekannte durchfallen lässt, erzeugt genau die Fehlalarme, wegen derer solche Abläufe nach drei Wochen wieder abgeschaltet werden.
Was dabei herauskommt
Vier Testdateien, ein Aufruf:
python3 pruefe_eingang.py testdaten/rechnung_en16931.xml testdaten/rechnung_zugferd.pdf \
testdaten/rechnung_minimum.xml testdaten/rechnung_ohne_daten.pdfDatei: rechnung_en16931.xml
Herkunft: XML-Datei
Profil: EN 16931
Positionen: 1
Rechnung RE-2026-0042 vom 03.09.2026, Bauer Elektrotechnik GmbH, 499.80 EUR
ANGENOMMEN Profil erfuellt die Pflicht
Datei: rechnung_zugferd.pdf
Herkunft: eingebettet in PDF als factur-x.xml
Profil: EN 16931
Positionen: 1
Rechnung RE-2026-0042 vom 03.09.2026, Bauer Elektrotechnik GmbH, 499.80 EUR
ANGENOMMEN Profil erfuellt die Pflicht
Datei: rechnung_minimum.xml
Herkunft: XML-Datei
Profil: MINIMUM
Positionen: 0
Rechnung RE-2026-0042 vom 03.09.2026, Bauer Elektrotechnik GmbH, 499.80 EUR
ZURUECK Profil MINIMUM enthaelt nicht alle Pflichtangaben strukturiert
und gilt nicht als E-Rechnung
Datei: rechnung_ohne_daten.pdf
ABGELEHNT keine E-Rechnung: PDF ohne eingebettete Daten{
"datei": "rechnung_minimum.xml",
"erechnung": true,
"profil": "MINIMUM",
"erfuellt_pflicht": false,
"positionen": 0,
"felder": {
"rechnungsnummer": "RE-2026-0042",
"datum": "03.09.2026",
"verkaeufer": "Bauer Elektrotechnik GmbH",
"gesamtbetrag": "499.80",
"waehrung": "EUR"
},
"fehlend": [],
"hinweis": "Profil MINIMUM enthaelt nicht alle Pflichtangaben strukturiert und gilt nicht als E-Rechnung",
"profil_urn": "urn:factur-x.eu:1p0:minimum",
"herkunft": "XML-Datei",
"urteil": "ZURUECK"
}"fehlend" ist leer. Wer seine Prüfung nur an dieser Liste aufhängt, hat den Fall nicht gefunden. Deshalb entscheidet bei uns das Profil und nicht die Vollständigkeit der fünf Felder.
In n8n einhängen
Der Ablauf davor holt den Anhang aus der Mail und legt ihn ab. Dieser Schritt hängt direkt dahinter und hat genau zwei Ausgänge: weiter oder zurück an den Absender.
- Ein Execute-Command-Knoten ruft das Skript mit dem Dateipfad und --json auf.
- Ein Code-Knoten mit einer Zeile macht aus stdout ein Objekt, denn Execute Command liefert nur Text.
- Ein IF-Knoten verzweigt auf erfuellt_pflicht.
- Der Nein-Zweig schreibt eine Mail an den Absender, in der hinweis wörtlich vorkommt. Das erspart das Telefonat, in dem niemand weiß, was gemeint ist.
- Der Ja-Zweig übergibt felder an den nächsten Schritt.
Wer n8n nicht selbst hostet, nimmt statt des Execute-Command-Knotens einen Code-Knoten und portiert die drei Funktionen. Die Logik ist kurz genug dafür; die Profil-Liste ist ohnehin nur eine Tabelle.
Was die Prüfung nicht kann
Drei Grenzen, und alle drei sind bewusst so:
- Sie prüft nicht gegen die Geschäftsregeln der EN 16931. Ob der Steuerbetrag zur Bemessungsgrundlage passt, sagt sie nicht. Dafür braucht es ein Werkzeug wie Mustangproject, und das ist ein eigener Schritt.
- Sie erkennt UBL-Rechnungen, prüft sie aber nicht. In Deutschland kommt überwiegend CII an; UBL taucht vor allem bei ausländischen Absendern auf. Wer viele davon bekommt, muss hier nacharbeiten.
- Sie sagt nichts darüber, ob die Rechnung inhaltlich stimmt. Eine Dublette, ein falscher Betrag, eine fehlende Position im eigenen Auftrag: alles nicht ihr Thema. Das ist der nächste Beitrag.
Zum Nachbauen
Das Paket zu diesem Beitrag enthält das Skript, den Erzeuger für die Testdateien, die vier Testdateien und den n8n-Ablauf als Export. Entpacken, pypdf und lxml installieren, dann:
- pruefe_eingang.py: das Skript aus diesem Beitrag, vollständig; mit --json für n8n
- erzeuge_testdaten.py: schreibt die vier Testdateien neu, mit reportlab für die Ansicht und pypdf für Anhang und Metadaten
- testdaten/: die vier Dateien, mit denen die Ausgabe oben entstanden ist
- n8n/eingangspruefung.json: der Ablauf mit Execute-Command-, Code- und IF-Knoten und den zwei Zweigen dahinter; Zugangsdaten sind Platzhalter
Eingangsprüfung E-Rechnung: Skript, Testdateien und n8n-Ablauf
HerunterladenStand dieses Artikels
Gebaut und geprüft am 11. September 2026 mit Python 3.11.15, lxml 6.1.0 und pypdf; die Testdateien dieser ersten Fassung kamen aus drafthorse. Am 18. September 2026 noch einmal geprüft mit Python 3.12.2, lxml 5.2.1 und pypdf 6.19.0, diesmal mit den Testdateien aus erzeuge_testdaten.py, damit niemand drafthorse installieren muss; die Ausgabe oben ist die dieses Laufs. Zur Gegenprobe haben wir dieselben vier Dateien durch Mustang-CLI 2.26.0 geschickt: beide XML-Dateien gültig in ihrem jeweiligen Profil, die XML in der PDF gefunden und gültig, die PDF ohne Daten abgelehnt. Die Einordnung der Profile folgt dem BMF-Schreiben zur obligatorischen E-Rechnung vom 15. Oktober 2025, Randziffer 14. Der n8n-Export ist von Hand gegen die Knotentypen und Feldnamen geschrieben, aber nicht gegen ein laufendes n8n geprüft. Geprüft wurde bisher nur gegen selbst erzeugte Dateien; sobald genug echte Eingänge durchgelaufen sind, ergänzen wir, was dabei aufgefallen ist.
Häufige Fragen
Muss ich eine eingehende E-Rechnung überhaupt prüfen?
Verpflichtet bist du dazu nicht. Aber du bist verpflichtet, sie zu empfangen und aufzubewahren, und du ziehst Vorsteuer daraus. Eine Datei, die das Profil MINIMUM trägt, enthält die Angaben dafür nicht strukturiert. Es ist billiger, das beim Eingang zu merken als im Jahresabschluss.
Warum reicht es nicht, auf leere Felder zu prüfen?
Weil das Profil MINIMUM alle Kopfdaten überträgt und trotzdem keine E-Rechnung ist. Rechnungsnummer, Datum, Verkäufer und Gesamtbetrag sind gefüllt, die Positionen fehlen. Eine Prüfung, die nur nach leeren Feldern sucht, winkt den Fall durch.
Was mache ich mit einer Datei, deren Profil unbekannt ist?
Von Hand ansehen, nicht automatisch ablehnen. Neue Profilkennungen kommen mit neuen Versionen der Formate dazu. Ein Ablauf, der alles Unbekannte zurückweist, erzeugt Fehlalarme und wird nach kurzer Zeit abgeschaltet.

Rechnungen aus dem Postfach sortieren
Drei Schritte, ein lokales Modell, eine Namensregel. Im ersten Lauf blieben Mahnung und Gutschrift liegen, weil unser Code dem falschen Feld vertraute. Der Fehler, der Fix und was danach noch liegen bleibt.

Sprachnotiz in Text
Diktierte Notizen landen als sauberer Text in der Ablage, ohne Cloud. Zwei getrennte Schritte, der Rohtext bleibt liegen, und bei zwei Stimmen gleichzeitig verschwindet eine davon ohne Warnung.