Skrift Auto Article

Zwei n8n-Workflows fuer die Ratgeber-Produktion von skrift.de.

artikel-index.json          Redaktionsplan, eine Zeile je Artikel
   |
   v  Workflow 1: brief-vorbereiten
briefe/brief-<slug>.json    NeuronWriter-Brief als Schreibvorlage
   |
   v  Cowork schreibt den Artikel
artikel/artikel-<slug>.json fertiger Artikel
   |
   v  Workflow 2: pruefen-und-importieren
WordPress-Entwurf           +  nachbessern/nachbessern-<slug>.json bei zu niedrigem Score

1. Einmalige Einrichtung

1.1 Credentials in n8n

Die Workflows referenzieren drei Credentials, die bereits im n8n-Credential-Store angelegt sind. In den Node-Parametern steht kein einziges Geheimnis.

Credential Typ Inhalt
NeuronWriter API HTTP Header Auth Name X-API-KEY, Wert = NeuronWriter-Key
Gitea Skrift Auto Article HTTP Header Auth Name Authorization, Wert = token <gitea-token>
WP skrift.de Redakteur HTTP Basic Auth Benutzer ai-agent, Passwort = Application Password

Wird ein Key ausgetauscht, nur das Credential in n8n bearbeiten. Die Workflow-Dateien bleiben unveraendert.

Gitea-Token: braucht den Scope write:repository. Mit read:repository allein scheitert jeder Schreibvorgang mit HTTP 403 und der Meldung token does not have at least one of required scope(s).

Gitea-Adresse: beide Workflows sprechen Gitea als http://gitea:3000 an, also ueber das Docker-Netz, nicht ueber https://gitea.lucas-orth.de. Grund: der oeffentliche Hostname loest auf die NetBird-Adresse 100.98.99.19 auf, die der n8n-Host nur auf Port 80 erreicht, nicht auf 443. Details in docs/testlauf.md. Der Docker-Weg ist ausserdem schneller und unabhaengig vom Reverse Proxy. Aendern laesst sich das im Node Konfiguration, Feld gitea_base.

WordPress-Benutzer: eigener Benutzer mit Rolle Redakteur, kein Administrator. Die Rolle Redakteur darf technisch auch veroeffentlichen. Der Workflow setzt status: draft hart und bricht ab, falls WordPress etwas anderes zurueckmeldet. Wer das zusaetzlich absichern will, nimmt die Rolle Autor und entzieht per Code-Snippet publish_posts. Dann kann der Benutzer aber keine fremden Beitraege mehr aktualisieren, was die Idempotenz bricht.

1.2 Mu-Plugin auf skrift.de

wordpress/skrift-ratgeber-felder.php nach wp-content/mu-plugins/ kopieren.

Es registriert vier Meta-Felder REST-schreibbar:

Feld Typ Inhalt
kurzantwort string Ein Absatz, der die Suchfrage sofort beantwortet
cta_ziel string (URL) Zieladresse des Call-to-Action
stand_datum string Format YYYY-MM-DD
faq array [{frage, antwort}]

Ohne dieses Plugin nimmt die WordPress-REST-API die Felder stillschweigend nicht an. Der Workflow erkennt das und schreibt eine Warnung in die Benachrichtigung.

Ausgabe der FAQ im Beitrag ueber den Shortcode [skrift_faq titel="Haeufige Fragen"], zum Beispiel in einem Elementor-Shortcode-Widget. Die drei einfachen Felder lassen sich in Elementor Pro direkt als Dynamic Tag "Post Custom Field" einbinden.

1.3 SMTP fuer die Benachrichtigung

Beide Workflows enden auf einem Node Benachrichtigung, der deaktiviert ist. Zum Aktivieren: SMTP-Credential zuweisen, Absenderadresse eintragen, Node aktivieren. Die Meldung wird vom Node davor fertig formuliert, am Text selbst muss nichts geaendert werden.


2. Workflow 1: brief-vorbereiten

Start

  • Manuell in der n8n-Oberflaeche
  • Zeitplan Montag 07:00 (Node ist deaktiviert, bei Bedarf aktivieren)
  • Webhook: POST https://n8n.lucas-orth.de/webhook/brief-vorbereiten

Ablauf

  1. artikel-index.json ueber die Gitea-API lesen
  2. Alle Zeilen mit status: "offen" herausfiltern, Pflichtfelder pruefen
  3. Je Artikel POST /new-query an NeuronWriter
  4. POST /get-query pollen, alle 20 Sekunden, maximal 30 Versuche
  5. Antwort auf die benoetigten Felder reduzieren
  6. briefe/brief-<slug>.json committen
  7. Status auf brief_bereit setzen, query_id in die Zeile schreiben
  8. Benachrichtigung

Pflichtfelder in artikel-index.json

Die Datei ist ein Array. Jede Zeile braucht:

{
  "slug": "handgeschriebene-werbebriefe",
  "titel": "Handgeschriebene Werbebriefe: Wann sie wirken und wann nicht",
  "keyword": "handgeschriebene werbebriefe",
  "kategorie": "Direktmarketing",
  "cta_ziel": "https://skrift.de/muster-anfordern",
  "status": "offen"
}

Fehlt eines der Felder in einer Zeile mit status: "offen", bricht der Workflow mit Angabe von Zeilennummer und fehlenden Feldern ab. Zeilen mit anderem Status werden nicht geprueft.

Statuswerte: offen -> brief_bereit (durch Workflow 1) -> danach frei nutzbar.

Was im Brief steht

Die im Auftrag geforderten Felder liegen auf oberster Ebene: query_id, keyword, begriffe_pflicht, begriffe_erweitert, fragen, wortzahl_ziel.

begriffe_pflicht und begriffe_erweitert enthalten je Begriff die von NeuronWriter empfohlene Spanne:

{"begriff": "handschriftlich", "min": 1, "max": 17, "bei_wettbewerbern_pc": 70}

fragen fasst vier NeuronWriter-Quellen zusammen, dedupliziert und nach Wichtigkeit sortiert. Fragen aus der topic_matrix tragen ein gewicht von 1 bis 10.

Darunter steht artikel mit slug, titel, kategorie und cta_ziel aus der Tabelle, sowie ein Block zusatz mit Material, das beim Schreiben hilft: Ueberschriften-Begriffe, Entitaeten, Suchintention, Zielwortzahl-Median und die vollstaendigen Gliederungen der fuenf bestplatzierten Wettbewerber.


3. Workflow 2: pruefen-und-importieren

Start

  • Manuell
  • Webhook: POST https://n8n.lucas-orth.de/webhook/pruefen-und-importieren

Vertrag Cowork -> Workflow 2

Cowork legt je Artikel eine Datei artikel/artikel-<slug>.json an:

{
  "slug": "handgeschriebene-werbebriefe",
  "titel": "Handgeschriebene Werbebriefe: Wann sie wirken und wann nicht",
  "query_id": "565abcca58f65e52",
  "keyword": "handgeschriebene werbebriefe",
  "kategorie": "Direktmarketing",
  "cta_ziel": "https://skrift.de/muster-anfordern",
  "content_html": "<h1>...</h1><p>...</p>",
  "kurzantwort": "Ein Absatz, der die Hauptfrage direkt beantwortet.",
  "faq": [{"frage": "...", "antwort": "..."}],
  "seo_titel": "max. 60 Zeichen",
  "seo_beschreibung": "max. 155 Zeichen",
  "stand_datum": "2026-09-20"
}

Pflicht sind slug, titel, query_id, kategorie, content_html. Der Slug im Dateinamen muss mit dem Slug im JSON uebereinstimmen, sonst bricht der Workflow fuer diesen Artikel ab. query_id stammt aus dem Brief, Workflow 1 schreibt sie zusaetzlich in artikel-index.json.

Fehlt stand_datum, setzt der Workflow das heutige Datum. Fehlt seo_titel, wird titel verwendet.

Schreibregeln fuer content_html

Preise niemals ausschreiben. Sie werden zentral gepflegt und per Shortcode eingesetzt:

[skrift_preis key="SCHLUESSEL" ab="0|1" brutto="0|1"]
Parameter Bedeutung
key Produkt- oder Positionsschluessel, z. B. briefe, postkarten, porto_inland
ab=1 guenstigster Staffelpreis; ab=0 der Standardpreis
brutto=1 inkl. MwSt.; brutto=0 netto

Das Wort "ab" gehoert in den Fliesstext, der Shortcode liefert nur die Zahl:

Handschriftliche Briefe ab [skrift_preis key="briefe" ab="1"] pro Stueck.

Ein unbekannter key erzeugt eine leere Ausgabe, keinen Fehler.

Workflow 2 durchsucht content_html nach ausgeschriebenen Betraegen (2,56 EUR, 0,95 €) und meldet jeden Treffer in der Benachrichtigung. Der Import wird deswegen nicht abgebrochen, der Beitrag ist ja ein Entwurf.

FAQ nicht in content_html schreiben. Sie gehoeren in das Feld faq und werden im Beitrag ueber [skrift_faq] ausgegeben. Dasselbe gilt fuer die Kurzantwort, die als eigenes Feld kurzantwort transportiert wird.

Workflow 1 legt diese Regeln als Block schreibregeln in jeden Brief, damit sie beim Schreiben direkt vorliegen.

Ablauf

  1. Ordner artikel/ auflisten, alle artikel-*.json einsammeln
  2. Je Artikel: Datei laden, zugehoerigen Brief laden
  3. POST /evaluate-content an NeuronWriter, content_score auslesen
  4. Score unter Zielwert:
    • fehlende Begriffe gegen den Brief berechnen
    • nachbessern/nachbessern-<slug>.json committen
    • naechster Artikel
  5. Score ab Zielwert:
    • POST /import-content speichert die Revision in NeuronWriter
    • GET /wp-json/wp/v2/posts?slug=<slug> prueft auf vorhandenen Beitrag
    • Kategorie suchen, bei Bedarf anlegen
    • Beitrag anlegen oder aktualisieren, immer status: draft
    • Meta-Felder und SEOPress-Felder im selben Request
    • Cache leeren (siehe Hinweis unten)
    • Zeile in status-importe.json
  6. Benachrichtigung mit Vorschaulinks

Zielwert anpassen

Im Node Konfiguration, Feld score_ziel. Standard ist 72. Der Wert gilt fuer alle Artikel eines Laufs.

Fuer einen einzelnen Lauf laesst er sich ueber den Webhook uebersteuern, ohne den Workflow zu aendern:

curl -X POST https://n8n.lucas-orth.de/webhook/pruefen-und-importieren \
  -H "Content-Type: application/json" -d '{"score_ziel":60}'

Ohne Angabe bleibt es bei 72. Alle anderen Stellschrauben liegen im selben Node: Basis-URLs, Ordnernamen, Pause zwischen Artikeln, Empfaengeradresse.

Nachbesserungsbericht

/evaluate-content liefert ausschliesslich {status, content_score}, also keine Liste fehlender Begriffe. Der Workflow berechnet sie deshalb selbst, indem er den Artikeltext gegen begriffe_pflicht und begriffe_erweitert aus dem Brief zaehlt.

{
  "score": 44,
  "score_ziel": 72,
  "wortzahl": {"ist": 96, "ziel": 1178, "differenz": -1082},
  "fehlende_pflichtbegriffe": [{"begriff": "empfänger", "soll_min": 1, "soll_max": 9, "ist": 0, "luecke": 1}],
  "fehlende_zusatzbegriffe": [],
  "unbeantwortete_fragen": [{"frage": "...", "gewicht": 10}]
}

Die Zaehlung arbeitet als Teilstring auf kleingeschriebenem Text. Komposita zaehlen mit: brief wird auch in Werbebriefe gefunden. Umlaute werden nicht normalisiert. Steht im Artikel Empfaenger statt Empfänger, gilt der Begriff als fehlend. Das ist gewollt, weil NeuronWriter genauso zaehlt.


4. Fehlerverhalten

Jeder HTTP-Node hat einen eigenen Fehlerausgang mit einer Meldung, die die wahrscheinliche Ursache benennt statt nur den Statuscode.

Ein Artikel darf uebersprungen werden bei: Analyse-Start, Status-Abfrage, Poll-Timeout, Artikel-Datei laden, Score-Ermittlung, Nachbesserungs-Commit. Der Lauf geht dann mit dem naechsten Artikel weiter, der Fehler landet in der Benachrichtigung.

Ein Artikel darf nicht uebersprungen werden beim Schreiben nach WordPress. Der Node Beitrag anlegen oder aktualisieren hat bewusst kein Continue On Fail. Schlaegt er fehl, stoppt der gesamte Lauf, damit kein halb importierter Zustand unbemerkt bleibt. Dasselbe gilt fuer das Zurueckschreiben des Status in Workflow 1: bleibt die Tabelle auf offen, wuerden beim naechsten Lauf Analysen doppelt erzeugt und Kontingent verbraucht.

HTTP-Nodes wiederholen bei technischen Fehlern dreimal mit drei Sekunden Abstand. Zwischen zwei Artikeln liegt eine Pause von fuenf Sekunden.

Cache leeren gibt HTTP 403. Der Redakteur-Benutzer darf POST /wp-json/seopress/v1/commands/clear-cache nicht aufrufen. Das ist protokolliert und bewusst kein Abbruchgrund: der importierte Beitrag ist ein Entwurf, ein Cache-Purge waere wirkungslos. Relevant wird es erst beim Aktualisieren eines bereits veroeffentlichten Beitrags.

Achtung bei haengenden Zielen. Der timeout eines HTTP-Nodes greift erst nach dem Verbindungsaufbau. Ein Ziel, das SYN-Pakete verschluckt statt sie abzulehnen, laesst den Node unbegrenzt stehen, der Fehlerpfad feuert nie und die Execution blockiert. Dagegen hilft nur EXECUTIONS_TIMEOUT in der n8n-Umgebung.


5. Aufbau des Repos

artikel-index.json        Redaktionsplan
artikel/                  fertige Artikel aus Cowork
briefe/                   NeuronWriter-Briefe aus Workflow 1
nachbessern/              Berichte zu Artikeln unter Zielwert
status-importe.json       was wann als Entwurf nach WordPress ging
workflows/                die importfertigen n8n-Dateien, dazu netzcheck
build/                    Generatorskripte, die die Workflow-JSONs erzeugen
wordpress/                Mu-Plugin fuer die Meta-Felder
docs/                     Feldnachweis und Rohbelege der API-Antworten
.env                      Zugangsdaten, nicht versioniert
.env.example              Vorlage ohne Werte

Die Workflow-JSONs werden nicht von Hand bearbeitet, sondern aus build/gen_wf1.py und build/gen_wf2.py erzeugt:

python3 build/gen_wf1.py
python3 build/gen_wf2.py

Danach in n8n importieren oder per API aktualisieren. Wer lieber direkt in der n8n-Oberflaeche arbeitet, exportiert den Workflow anschliessend wieder nach workflows/ und laesst die Generatoren liegen.


6. Hilfsworkflow netzcheck

In n8n liegt netzcheck (Hilfswerkzeug). Er fragt aus n8n heraus eine beliebige URL ab und meldet Statuscode, Groesse und Fehlercode zurueck. Nuetzlich, wenn ein Ziel aus n8n nicht erreichbar scheint:

curl -X POST https://n8n.lucas-orth.de/webhook/probe \
  -H "Content-Type: application/json" \
  -d '{"url":"http://gitea:3000/api/v1/version"}'

7. Belege

docs/feldnachweis.md listet jede Stelle, an der ein Feldname aus einer echten API-Antwort ermittelt wurde, mit Beispielantwort. Die Rohantworten liegen als docs/beleg-*.json daneben.

docs/testlauf.md protokolliert den Testlauf vom 20.09.2026 gegen die echten Systeme, inklusive der beiden Fehler, die er gefunden hat, und des Netzwerkbefunds zu Gitea.

Description
No description provided
Readme 4.7 MiB
Languages
Python 67.2%
PHP 22.8%
JavaScript 6.8%
CSS 3.2%