AI in Content Marketing: How I Build a Content Pipeline with n8n
How I use n8n and an LLM to turn one topic into a pack of platform-specific drafts, and how to build the same pipeline with Gemini or DeepSeek, with a human still doing the publishing.

Most "AI for content marketing" advice is about which tool writes the best blog post. That was never my problem. My problem was the step after the idea: taking one topic and turning it into a YouTube script, a short, a carousel, a LinkedIn post, a thread and a blog draft, each in the format that platform expects. That work is repetitive, it has clear rules, and it is exactly the kind of job I automate for clients. So I treated my own content the same way: as a pipeline with an input, a process and an output.
This guide covers how AI actually helps in that pipeline, how my own n8n workflow is built, and how you can build a similar one with Google Gemini or DeepSeek. It also covers what I deliberately left manual, because that part matters as much as the automation.
Where AI helps in content marketing, and where it doesn't
I split content work into four stages and ask the same question of each: is this rule-following work or judgement work?
- Ideation. Choosing what to talk about depends on what I have built, what clients ask me and what I want to be known for. An LLM can expand a topic into angles, but the topic list stays mine.
- Drafting and repurposing. This is where AI earns its keep. Turning one topic into platform-shaped drafts follows rules: a carousel has a fixed number of slides, a short has a time budget, a LinkedIn post has a hook and a call to action. Rules can go into a prompt.
- Editing. Facts, examples from my own work, and the final voice need a human. Models write confident sentences about things they do not know.
- Publishing. Posting, scheduling and replying are where a mistake is public. I keep a person at that step.
So the pipeline I built automates the middle: one topic in, a pack of drafts out, stored where I can edit them. Nothing gets posted automatically.
My pipeline: the n8n Content Machine
My workflow is the n8n Content Machine, published as the "12-in-1 Content Planner" workflow export on GitHub. It turns one topic from a Google Sheet into drafts for 12 formats across 7 channels: YouTube long and short, Instagram reel, post and carousel, Facebook, Threads, an X post and an X thread, a LinkedIn post and article, and a blog post.
Here is the whole flow, node by node:
Manual trigger
-> Google Sheets: read rows where Status = READY
-> Google Gemini ("Message a Model"): one call, returns one JSON object for all 12 formats
-> Code node: strip code fences, parse the JSON, build a master doc + 12 sheet cells
-> HTTP Request: Google Docs API, create the master doc and insert the text
-> Google Drive: move the doc into the content folder
-> Google Sheets: write the 12 outputs back to the row, set Status = DONE
-> Code node: split into 12 per-format doc jobs
-> HTTP Request + Google Drive: create and file one Google Doc per format
The only input per topic is the Title column. The prompt does the heavy lifting: it carries my brand block, language rules (Hinglish for reel voiceovers and Instagram captions, simple English for carousel slides, professional English for LinkedIn and the blog) and format rules such as exactly 10 carousel slides and a 6 to 9 minute YouTube script with chapters. It also tells the model to avoid false claims and to phrase anything uncertain as a suggestion.
What it does not do is just as clear. There is no schedule trigger, no research or web search step, no image generation node and no posting to any platform. Image and thumbnail outputs are text prompts I can use later. Every run ends with documents in Drive and a filled row in the sheet, and I take it from there.
Owner-reported results: since I started running topics through this workflow, I estimate it saves me 16+ hours a week, and a topic that used to take me about 6 hours to repurpose by hand now takes about 30 minutes, roughly 10x faster. These are my own estimates from my own use, not measured benchmarks; the workflow export itself contains no timing data.
The design choice that made it reliable: one call, one JSON contract
My first instinct was to chain several prompts: research, outline, draft, polish. In practice, a single call with a strict output contract was easier to run and easier to debug. The prompt asks for one JSON object with a fixed key shape, and everything after the model is plain, deterministic JavaScript.
A trimmed version of the contract looks like this (the real one has a key for every format plus an assets block and a validation block):
{
"topic": "string",
"youtube_long": { "title": "string", "chapters": ["string"], "script": "string" },
"instagram_carousel": { "slides": ["string"] },
"linkedin_post": { "hook": "string", "body": "string", "cta": "string" },
"blog": { "title": "string", "meta_title": "string", "meta_description": "string", "body_markdown": "string" }
}
Why this works better than a chain for this job:
- One failure point. If the JSON does not parse, the run stops at one obvious node instead of producing half a pack.
- Testable code downstream. The Code node either finds
linkedin_post.bodyor it does not. You can test that without calling a model. - Cheap to switch models. Only the model node and the parser's "where is the text" line care which provider you use.
The trade-off is a long prompt and a long response, so give the model room (a sensible maximum token setting) and keep the per-field rules short.
The Code node that turns model output into documents
LLMs often wrap JSON in a Markdown code fence or add a sentence before it. The parser has to cope. This is a simplified version of the idea, written for an n8n Code node in Run Once for All Items mode. It is not the exact code from my repo, which is longer and builds the full documents.
// n8n Code node, JavaScript, "Run Once for All Items"
const raw = $input.first().json;
// Gemini node output: content.parts[0].text
// OpenAI-compatible output (DeepSeek via HTTP Request): choices[0].message.content
const text =
raw?.content?.parts?.[0]?.text ??
raw?.choices?.[0]?.message?.content ??
"";
// Remove code fences, then take the outermost JSON object
const cleaned = text.replace(/```(?:json)?/gi, "").trim();
const start = cleaned.indexOf("{");
const end = cleaned.lastIndexOf("}");
if (start === -1 || end === -1) {
throw new Error("Model did not return a JSON object");
}
const pack = JSON.parse(cleaned.slice(start, end + 1));
return [
{
json: {
linkedin_post: [pack.linkedin_post?.hook, pack.linkedin_post?.body, pack.linkedin_post?.cta]
.filter(Boolean)
.join("\n\n"),
carousel: (pack.instagram_carousel?.slides ?? [])
.map((s, i) => `Slide ${i + 1}: ${s}`)
.join("\n"),
blog_markdown: pack.blog?.body_markdown ?? "",
},
},
];
Two lessons from reading my own workflow back carefully:
- Use
$input.all()if you want several topics per run. My parser reads$input.first(), so even if three rows areREADY, one execution handles one topic. That is fine for a manual trigger, but it surprises people who expect a batch. - Keep the parser and the prompt in sync. A few of my per-format docs read field names the prompt never asks for, so parts of them come out empty, while the master doc uses the right fields. When you change the prompt's JSON shape, search the Code nodes for every field name.
Building it with Gemini or DeepSeek
Option A: the Google Gemini node
This is what my export uses. Add the Google Gemini node, choose the Text → Message a Model operation, connect a Google Gemini (PaLM) credential, and pick a model from the node's list. My export was built on a Gemini 3 Pro preview model; preview models come and go, so choose whatever your account offers today. Put the JSON contract and the rules in the prompt and reference the sheet column with an expression such as {{ $json.Title }}.
Option B: DeepSeek
DeepSeek's API is OpenAI-compatible, with the base URL https://api.deepseek.com. When I checked the docs on 2026-10-03, the listed models were deepseek-flash and deepseek-v4-pro. Older names you may see in tutorials are routed or retired, so check the models page before you copy any model name, including mine.
In n8n you have two routes:
- DeepSeek Chat Model node. It is a sub-node: it plugs into an AI root node rather than running alone. It has options for Response Format, Maximum Number of Tokens and Sampling Temperature, and uses a DeepSeek credential.
- HTTP Request node.
POST https://api.deepseek.com/chat/completionswith your key in a Header Auth credential and the request below as the JSON body. The parser above already readschoices[0].message.content.
DeepSeek's JSON Output mode needs three things: response_format set to {"type": "json_object"}, the word "json" somewhere in the prompt along with an example of the shape you want, and a max_tokens value high enough that the JSON is not cut off. The docs also warn that the API may occasionally return empty content, so handle that case.
Here is the same call in Python with the OpenAI SDK, useful if you prototype the prompt outside n8n first:
import json
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url="https://api.deepseek.com",
)
SYSTEM = (
"You turn one topic into platform drafts. "
"Return only a json object with the keys: topic, linkedin_post "
"(hook, body, cta), instagram_carousel (slides: exactly 10 strings), "
"blog (title, meta_title, meta_description, body_markdown). "
"Do not invent statistics; phrase uncertain points as suggestions."
)
def content_pack(topic: str) -> dict:
response = client.chat.completions.create(
model="deepseek-flash",
messages=[
{"role": "system", "content": SYSTEM},
{"role": "user", "content": f"Topic: {topic}"},
],
response_format={"type": "json_object"},
max_tokens=8000,
)
text = response.choices[0].message.content
if not text:
raise RuntimeError("Empty response from the model; retry this topic")
return json.loads(text)
if __name__ == "__main__":
pack = content_pack("Why I keep a human in the publishing step")
print(pack["linkedin_post"]["hook"])
The max_tokens value here is a starting point, not a recommendation from DeepSeek; size it to your contract.
One caution from my own repo: the original README listed DeepSeek and OpenAI as drop-in alternatives, but the exported workflow only contains the Gemini node and its parser expected Gemini's output shape. If you switch providers, update the parser's "where is the text" line, as in the Code node above.
Guardrails I would not skip
Automation makes it cheap to produce a lot of mediocre content. These checks keep the pipeline honest without heavy tooling:
- A status column as the gate. Only rows marked
READYare processed and the workflow setsDONE. Add your ownREVIEWEDandPUBLISHEDstates so nothing moves forward without you. - A rule against invented facts in the prompt. It does not guarantee anything, but it reduces confident numbers appearing from nowhere. Then check every number and claim yourself before posting.
- Fail loudly. My export has no error branch or retry, which is the next thing I would add. A failed parse should notify you, not quietly leave a row half-filled. n8n lets you add an error workflow for that.
- Drafts, not posts. Keeping the output in Docs and Sheets means a bad generation costs me a delete, not a public correction.
- Keep identifiers out of shared templates. If you share your workflow export, replace your sheet ID, folder ID and similar identifiers with placeholders first.
Making it run on its own (when you're ready)
My workflow uses a manual trigger on purpose: I decide when a topic is ready. If you want it to pick up new rows automatically, swap the trigger for a Schedule Trigger node. The n8n docs note that a workflow using the Schedule node must be saved and published for the schedule to run. Pair it with the $input.all() change so each run handles every READY row, and add the error workflow first, because unattended runs fail unattended too.
The natural next step after text is images. If you want hero images in the same pipeline, I wrote up how to deploy your own image generation API on Cloudflare Workers and call it from an n8n HTTP Request node. For broader repurposing patterns, see how I turn one technical post into ten assets, and for n8n basics, the n8n automation guide.
Frequently asked questions
Does AI-generated content hurt SEO?
How a draft was produced matters less than whether the published page is useful and accurate. In practice that means AI drafts plus real editing, real examples and checked facts. A pipeline that publishes raw model output at volume is the risky version.
Which model should I use?
The one whose output you like after editing, at a price you are comfortable with. The pipeline shape barely changes: swap the model node and adjust one line in the parser. Check the provider's current model list rather than trusting a name in a tutorial.
Do I need to be a developer?
You need to be comfortable reading JSON and editing a short JavaScript Code node. The rest is n8n configuration. If you can debug why a field is empty, you can run this.
Can it publish to social platforms automatically?
It could, with more nodes, but mine does not, by choice. Publishing is the step where a wrong fact or an odd sentence is public, so I keep it manual.
How long does it take to build?
That depends on how much of your prompt you already have. Start with the smallest version: sheet in, one model call, one Google Doc out. Add formats one at a time.
Start small
Pick one repetitive content task, such as turning a blog post into a LinkedIn post. Build it as sheet in, model call with a JSON contract, parser, doc out. Run it on a few topics, edit the drafts, and only then add the next format. That is how my own workflow grew, and it is the same advice I give clients: the smallest reliable pipeline first, more stages only when you know which step is slowing you down.
Sources
Verified against the sources below on October 3, 2026. Products and docs change often: check the linked sources if something looks different.



Comments
Loading comments...