DEV Community

Noah Berg
Noah Berg

Posted on

The one n8n node I make every agency add before AI touches a social account

Most "AI writes your social posts" workflows I see in n8n have the same shape. Trigger, an LLM node, then straight into a publish call. It works great in the demo. Then it posts a draft with a placeholder link to a client's LinkedIn at 7am on a Sunday and someone has a very bad Monday.

The fix is boring. Put a human in the middle, and make the workflow physically unable to skip them.

This is the pattern I use with agency teams. It's not an import-ready template (your endpoints, credentials and approval tool will differ), more of a wiring guide you can rebuild in 20 minutes.

The shape

  1. Brief comes in (Notion trigger, webhook, or manual trigger while testing)
  2. Normalize it into one predictable object
  3. Validate the brief, stop early if it's missing stuff
  4. AI drafts copy (HTTP Request to whatever model you use)
  5. Check the AI output before anyone sees it
  6. Save it as a draft in your scheduling tool, not a scheduled post
  7. Ping a reviewer
  8. Wait for a decision
  9. Route on approved / rejected / needs_revision
  10. Only approved items reach the schedule or publish node
  11. Log everything

Steps 6 to 10 are the whole point. Everything else is plumbing.

Normalize first, it saves you later

Briefs from Notion, a form and a Slack message all look different. Map them into one shape with an Edit Fields or Code node before anything else touches them. Something like:

{
  "briefId": "brief-001",
  "client": "acme",
  "channels": ["linkedin", "instagram"],
  "audience": "ops managers at mid size logistics companies",
  "keyPoints": ["new tracking dashboard", "no price talk"],
  "requestedPublishAt": "2026-10-08T09:00:00+02:00",
  "owner": "owner@example.invalid",  "approval": { "status": "pending", "reviewer": null, "comment": null }
}
Enter fullscreen mode Exit fullscreen mode

Then an IF node that refuses to continue without briefId, client, channels and owner. It sounds fussy but half the weird AI output I've debugged came from a brief that was basically empty.

Check the AI output with dumb code

Before a human sees the draft, run a small Code node that looks for the obvious stuff:

  • text longer than the platform limit for that channel
  • leftover placeholders like [LINK] or {{name}}
  • links that aren't on an allowlist of domains for that client
  • words the client has banned (every client has a list, ask for it)

If it fails, send it back to the AI step with the reason, or just stop and alert the owner. You do not need another LLM to judge the first LLM here. A regex catches most of it.

Draft, don't schedule

This is the step people skip. Whatever tool does your publishing, the first write from the workflow should create a draft or a pending item, not a scheduled post. If the AI node and the workflow both break at once, the worst case is a messy draft, not a live post.

Keep the actual publish node disabled while you test. I mean literally toggled off in n8n. Turn it on last.

The approval gate

n8n gives you a few ways to pause: a Wait node resumed by webhook, an n8n Form, or a sub-workflow that posts to Slack with buttons. Any of them work. What matters:

  • the reviewer sees the exact final copy, media, channel and time. Not a summary
  • the decision is one of three exact values: approved, rejected, needs_revision. A Switch node on a free text field is how you get "ok lol" treated as approval
  • there's a timeout, and on timeout the item stays pending or gets rejected. Never auto approve on timeout. I've seen this one. It's always the post nobody wanted
  • the reviewer is someone allowed to approve for that client, not whoever clicked first

needs_revision loops back to the AI step with the reviewer's comment attached. Cap the loop at 2 or 3 rounds, after that a human should just write it.

Idempotency, because webhooks retry

If your approval callback fires twice (it will, eventually), you don't want two posts. Carry the draft ID through the whole run and have the publish step check whether that draft was already scheduled before doing anything. Cheap to add, annoying to debug in production.

Log it

Last node writes the brief ID, AI output, edits, reviewer, decision time and the response from the publish call to wherever you keep records. Notion database, n8n Data Store, Postgres, a Google Sheet if that's your thing. When a client asks "who approved this?" three months from now you want an answer in 10 seconds.

Credentials

Goes without saying but I'll say it. Model keys and API tokens live in n8n credentials or env vars, never pasted into a Code node or the exported JSON. If you share the workflow, export it and grep it for anything that looks like a key before posting. Templates with hard coded secrets get rejected from the n8n gallery anyway.

What this looks like in practice

A stack that works well for agency teams: Notion for briefs, n8n in the middle, Claude for first drafts, Slack for the reviewer ping, and ContentStudio holding the drafts and doing the scheduling once something is approved (disclosure: I work at ContentStudio, so obviously that's the one I reach for). Swap any of those out. The pattern doesn't care which scheduler you use, as long as it has a real draft state and you never point an AI node directly at a publish endpoint.

If you've built something like this and found a better way to handle the revision loop I'd genuinely like to hear it. Mine still feels a bit clunky past round two.

Top comments (0)