In Teil 1 habe ich behauptet, die Tabelle zwischen den beiden Workflows sei das inhaltliche Zentrum der ganzen Konstruktion. Dieser Teil löst die Behauptung ein.
Die Tabelle ist in n8n eine Data Table – eine eingebaute Speicherform, die keine Zugangsdaten und keinen externen Dienst braucht. Für so etwas hätte ich früher eine Google-Tabelle genommen. Die Data Table ist schneller, hat keine API-Grenzen und liegt dort, wo die Workflows ohnehin laufen. Für Datenmengen in dieser Größenordnung ist sie die richtige Wahl.
Der Aufbau
Elf Spalten, jede mit einem Zweck:
| Spalte | Zweck |
|---|---|
post_id | die WordPress-Beitrags-ID – der Schlüssel für alles |
titel, link | zur Orientierung beim Draufschauen |
bild_url | die Quelle des Bildes |
text_facebook, text_instagram | die fertigen Texte |
status | der Zustand des Eintrags |
eingetragen_am | wann erzeugt |
geplant_fuer | wann veröffentlichen |
gepostet_am | wann tatsächlich rausgegangen |
fb_post_id, ig_post_id | Belege der erzeugten Beiträge |
hinweis | Herkunft, Bildquelle, Fehlertext |
Der Status kennt sechs Werte:
wartet_freigabe → freigegeben → gepostet
→ abgelehnt
→ fehler
gesperrt
Mehr Zustände braucht es nicht. Jede Zeile hat zu jedem Zeitpunkt genau einen, und man sieht auf einen Blick, wo etwas hängt.
Warum nicht Google Sheets
Der Reflex bei so einer Aufgabe ist eine Tabelle bei Google. Ich habe das jahrelang so gemacht, und es hat Vorzüge: Man kann von überall hineinschauen, mehrere Leute können daran arbeiten, und für Auswertungen gibt es Formeln.
Gegen Google Sheets sprachen hier drei Punkte.
Ratenbegrenzung. Die Sheets-API hat Grenzen pro Minute und pro Nutzer. Bei einem Massenlauf, der neunundvierzig Zeilen nacheinander schreibt, ist man schnell in der Nähe. Man kann Pausen einbauen – aber dann baut man Pausen ein, um ein Werkzeug zu schonen, das man nur als Speicher benutzt.
Ein zusätzlicher Zugang. Jede externe Verbindung ist ein Ding, das ablaufen, entzogen oder blockiert werden kann. Die Automatisierung hing ohnehin schon an WordPress, an Meta und an einem Modellanbieter. Eine vierte Abhängigkeit für reine Zustandshaltung war schwer zu rechtfertigen.
Datentypen. In einer Tabellenkalkulation ist alles Text, bis es zufällig etwas anderes wird. Ein Datum kann als Zeichenkette ankommen, ein führendes Plus verwandelt sich in eine Formel. Die Data Table kennt echte Typen – number, string, date, boolean – und beim Filtern auf „Termin liegt in der Vergangenheit“ ist das kein Komfort, sondern Voraussetzung.
Was man aufgibt: den Blick von außen. Wer die Warteschlange ansehen will, muss sich in n8n anmelden. Für eine Ein-Personen-Redaktion ist das kein Verlust; sobald mehrere Leute Beiträge freigeben sollen, würde ich neu abwägen.
Der Doppel-Schutz, der zum Universalwerkzeug wurde
Das erste Problem jeder Sammel-Automatisierung: Sie darf denselben Beitrag nicht zweimal aufnehmen. Der stündliche Lauf sieht ja jedes Mal dieselben Beiträge der letzten vierzehn Tage.
n8n hat dafür eine Operation, die genau das tut: rowNotExists. Sie bekommt eine Liste von Elementen und lässt nur die durch, die noch nicht in der Tabelle stehen. Ein Node, ein Filterkriterium – post_id –, fertig.
Interessant wurde dieser Baustein, als eine ganz andere Anforderung kam: Vierzehn alte Beiträge sollten dauerhaft nie erscheinen. Teils wegen der Plattformrichtlinien, teils weil ihre Zahlen von 2018 stammen.
Mein erster Gedanke war eine Ausschlussliste im Code. Dann fiel mir auf, dass der Doppel-Schutz die Arbeit bereits erledigt: Was in der Tabelle steht, wird nicht erneut aufgenommen. Also habe ich die vierzehn als Zeilen mit Status gesperrt eingetragen – Titel, Link und im Feld hinweis der Grund.
Das Ergebnis:
- Kein Sammel-Workflow nimmt sie je wieder auf, weil
rowNotExistsgreift - Der Posting-Workflow zieht sie nicht, weil er nur nach
freigegebensucht - Der Grund steht dokumentiert in derselben Zeile
- Rückgängig heißt: Zeile löschen
Kein zusätzlicher Filter, kein zweiter Ort zum Pflegen. Wenn dir bei einer Automatisierung eine zweite Liste einfällt, prüfe erst, ob ein vorhandener Mechanismus die Aufgabe schon mit erledigt.
Vom Takt zum Termin
Anfangs arbeitete der Posting-Workflow denkbar einfach: Er holte den ältesten Eintrag mit Status freigegeben und veröffentlichte ihn. Zwei feste Slots am Tag, macht vierzehn Posts pro Woche.
Bis die Anforderung kam, sieben bis zehn pro Woche zu posten – nicht vierzehn.
Es gab zwei Wege dorthin. Der eine: eine unsichtbare Drossel. Der Workflow zählt vor jedem Post, wie viele Beiträge in den letzten sieben Tagen rausgingen, und hält an, wenn das Kontingent erschöpft ist. Ein Node mehr, keine Schemaänderung, sofort fertig.
Der andere: ein Termin pro Zeile. Aufwändiger, aber sichtbar.
Ich habe mich für den Termin entschieden, und der Grund ist nicht technisch: Eine Drossel kann man nicht ansehen. Ein Kalender schon. Man kann hineinschauen, einen Eintrag vorziehen, einen anderen verschieben – von Hand, in der Tabelle, ohne den Workflow anzufassen.
Faustregel: Wenn zwei Lösungen gleich gut funktionieren, gewinnt die, die man sehen kann.
Wie der Takt entsteht
Die Umsetzung ist eine Rechnung mit zwei Zeilen. In der Konfiguration steht posts_pro_woche, derzeit 8. Daraus ergibt sich der Abstand:
168 Stunden ÷ 8 = 21 Stunden
Beim Einreihen liest der Workflow den spätesten bereits vergebenen Termin und addiert für jeden neuen Eintrag ein weiteres Vielfaches von 21 Stunden. Neue Einträge hängen sich also hinten an, ohne Lücke und ohne Überschneidung.
Der Posting-Workflow filtert dann auf:
status = "freigegeben" UND geplant_fuer <= jetzt
sortiert aufsteigend nach geplant_fuer und nimmt den ersten.
Hier steckt eine Feinheit, die ich zuerst übersehen hatte. Der Takt kommt aus dem Abstand, nicht aus den Slots. Die Slots um 12:00 und 18:30 bestimmen nur, wann am Tag geprüft wird – nicht, wie viel rausgeht. Vierzehn Slots pro Woche für acht fällige Einträge bedeuten schlicht: Sechsmal ist nichts fällig, und dann passiert nichts.
Das ist kein Fehler, sondern die gewünschte Eigenschaft. Der Workflow läuft leer durch, wenn nichts ansteht – kein Node schlägt fehl, keine Fehlermail. In n8n muss man dafür nur der Versuchung widerstehen, alwaysOutputData einzuschalten: Ein leeres Ergebnis ist bei einem Zeitplan-Workflow die normale Antwort auf „nichts zu tun“, kein Ausnahmefall.
Neue Beiträge drängeln sich vor
Ein Detail, das die Konstruktion rund macht: Neue Blogbeiträge bekommen beim Einreihen geplant_fuer = jetzt, sind also sofort fällig. Beiträge aus dem Altbestand bekommen einen Termin in der Zukunft.
Damit sortiert sich die Warteschlange von selbst richtig: Aktuelles geht vor, der Bestand füllt die Lücken. Es braucht keine Prioritätsspalte und keine Sonderbehandlung – die Sortierung nach Termin erledigt es.
Will man den Takt ändern, ändert man posts_pro_woche. Sieben ergibt 24 Stunden Abstand, zehn ergibt 16,8. Das wirkt auf künftige Einreihungen; bestehende Termine bleiben stehen und lassen sich in der Tabelle einzeln überschreiben.
Die Freigabe – und wann man sie weglässt
Für neue Blogbeiträge holt der Workflow eine Freigabe ein, bevor etwas rausgeht. n8n hat dafür eine Operation, die ich vorher nicht kannte: sendAndWait. Der Gmail-Node verschickt eine Mail mit zwei Schaltflächen und hält die Ausführung an, bis geklickt wird. Kein eigener Webhook, keine Zustandsverwaltung.
Die Mail zeigt Bild, Facebook-Text und Instagram-Text. Ein Klick auf Freigeben setzt den Status auf freigegeben, Ablehnen auf abgelehnt. Nach drei Tagen ohne Antwort läuft die Wartezeit ab.
Beim Altbestand habe ich bewusst darauf verzichtet. Neunundvierzig Beiträge auf einmal einzureihen hätte neunundvierzig Mails bedeutet – und niemand liest die vierzigste noch. Dort schreibt der Workflow direkt freigegeben, und die Kontrolle findet an anderer Stelle statt: Alle Texte stehen in der Tabelle, und wer einen nicht mag, setzt seinen Status auf abgelehnt.
Freigabeschleifen skalieren nicht. Bei Einzelstücken sind sie richtig, bei Massenläufen werden sie zur Formalie, die niemand ernst nimmt. Dann ist eine nachträgliche Prüfmöglichkeit ehrlicher.
Der Fehler, der 48 Beiträge verschluckte
Zum Schluss der Fehler dieses Teils – ein hübsches Beispiel dafür, wie still eine Automatisierung scheitern kann.
Der Massenlauf für den Altbestand sollte 49 Beiträge einreihen. Er lief neun Sekunden und meldete Erfolg – dieselbe Sorte stiller Fehlschlag wie bei den Yoast-Feldern, die WordPress kommentarlos wegwirft. In der Tabelle stand genau ein neuer Eintrag.
Kein Fehler, keine Warnung. Die Ursache steckte in einem Code-Node, der aus dem HTML die Gliederung zieht. Ich hatte ihn beim ersten Bauen für genau ein Element geschrieben:
const p = $input.first().json;
// … verarbeiten …
return [{ json: { … } }];
$input.first() nimmt das erste Element. Die anderen 48 fallen lautlos unter den Tisch – der Node hat ja korrekt gearbeitet, nur eben an einem Element statt an allen.
Die Reparatur ist offensichtlich, sobald man das Problem sieht:
const alle = $input.all();
const ergebnis = [];
for (let i = 0; i < alle.length; i++) {
const p = alle[i].json;
// … verarbeiten …
ergebnis.push({
json: { … },
pairedItem: { item: i }
});
}
return ergebnis;
Der zweite Teil ist genauso wichtig wie der erste: pairedItem. In n8n verfolgt dieses Feld, welches Ausgabeelement aus welchem Eingabeelement entstanden ist. Fehlt es, brechen in nachgelagerten Nodes alle Ausdrücke der Form $('Anderer Node').item.json.feld – n8n kann die Zuordnung dann nicht mehr herstellen. Bei einem Element fällt das nicht auf, bei fünfzig schon.
Woran man solche Fehler erkennt: Ein Massenlauf, der verdächtig schnell fertig ist. Neun Sekunden für 49 Modellaufrufe war unmöglich – das hätte mir sofort auffallen müssen. Die Laufzeit ist bei Stapelverarbeitung eine brauchbare Plausibilitätsprüfung. Nach der Reparatur brauchte derselbe Lauf 59 Sekunden, und das passte.
Was die Tabelle heute leistet
Nach dem korrigierten Lauf enthält sie 49 terminierte Beiträge, verteilt vom 23. August bis zum 4. Oktober, dazu die vierzehn gesperrten und die bereits veröffentlichten mit ihren Post-IDs als Beleg.
Damit ist sie das, was ich in Teil 1 angekündigt habe: Redaktionsplan, Freigabemappe, Doppel-Schutz, Protokoll und Sperrliste in einem – und alles davon in einer Ansicht, die man in zehn Sekunden überblickt.
Der Aufwand dafür waren elf Spalten und eine Handvoll Filterbedingungen.
Die Serie:
- Teil 1: Warum Texten und Posten getrennt gehören
- Teil 2: Texte, die etwas sagen
- Teil 3: Die Warteschlange als Redaktionsplan – dieser Beitrag
- Teil 4: Posten auf Facebook und Instagram
- Teil 5: Altbestand nachziehen und der Betrieb



