I used to spend 15 hours every week on content — planning topics, writing drafts, sourcing images, formatting posts. Then I built an n8n pipeline that handles all of it automatically. Now it produces 5 SEO-optimised articles every Monday morning, each with a unique AI-generated thumbnail, published directly to Blogger — while I'm at work.
In this guide, I'll walk you through building the same pipeline from scratch. You'll get the complete n8n workflow as a free downloadable JSON, the exact API prompts I use, and a cost breakdown showing why this setup costs about $0.03 per article — not $50.
1. What You'll Build — The Complete Pipeline
This is a macro-automation: a multi-step system that replaces an entire content production workflow, not just a single task. Every Monday at 9 AM, your n8n instance will wake up and run through this sequence without touching your keyboard:
⏰ Schedule Trigger
↓
🧠 DeepSeek — Generate 5 topics (JSON)
↓
🔄 Split In Batches (1 topic at a time)
↓
✍️ DeepSeek — Write full article (~1,200 words)
↓
🖼️ Gemini Imagen 3 — Generate thumbnail
↓
📤 Blogger API — Publish post
↓
⏳ Wait 3s → loop back for next topic
2. Why an AI Content Factory — and Why DeepSeek
Most automation tutorials teach you to connect two apps: Google Sheets to Gmail, a form to a spreadsheet. That's useful. This is different: it replaces the entire cycle of content ideation → research → writing → image sourcing → publishing.
The practical advantages are straightforward:
- Consistent publishing cadence — Google's ranking systems favour sites that publish regularly. This pipeline removes the human bottleneck entirely.
- Lower cost per article — DeepSeek charges $0.14 per million tokens, compared to GPT-4's $15–$30. For content generation, the quality difference is negligible for technical how-to articles.
- Free image generation — Google's Gemini Imagen API includes a generous free tier that covers 100+ images per day.
- Your time goes to editing and strategy — the parts only a human with real experience can do well.
3. Tools You'll Need
We'll connect four services inside n8n:
- n8n — the automation backbone. Cloud free tier or self-hosted on a $5 VPS.
- DeepSeek API — for topic generation and article writing. 1M-token context window, low cost.
- Google Gemini Imagen 3 — for generating featured images. Free tier: up to 60 requests per minute.
- Blogger API v3 — for automated publishing using OAuth 2.0.
gemini-pro-vision is a vision analysis model — it reads images, it doesn't create them. For image generation, you need the imagen-3.0-generate-001 endpoint. This is the most common mistake in n8n image automation tutorials.
4. Prerequisites — API Keys and Setup
- n8n instance — Cloud free tier works for testing. For production (longer timeouts, no execution limits), self-host on any VPS. If your self-hosted instance shows "localhost" in webhook URLs, fix that first: How to Fix 'Webhook URL Shows Localhost' Error in n8n.
- DeepSeek API key — Sign up at platform.deepseek.com. New accounts receive $5 in free credits — enough for ~500 articles.
- Google Gemini API key — Get yours from Google AI Studio (aistudio.google.com, not the old makersuite URL). Free tier includes 60 requests per minute.
- Blogger Blog ID + OAuth 2.0 credentials — See the FAQ below for step-by-step instructions. Your Blog ID is the number in your Blogger dashboard URL.
If you run into execution errors during setup, the complete troubleshooting guide for 'Workflow Execution Failed' covers the most common causes including timeout issues and node configuration errors.
5. Step-by-Step Build Guide
Create the Schedule Trigger
Open n8n, create a new workflow, and add a Schedule Trigger node. Use a cron expression for reliability — the UI picker sometimes misinterprets weekly intervals.
// In the Schedule Trigger node, choose "Cron Expression" mode // This fires every Monday at 09:00 AM (server timezone) 0 9 * * 1 // To run daily instead: 0 9 * * * // To run every 6 hours (high-volume mode): 0 */6 * * *
Generate 5 Topics with DeepSeek
Add an HTTP Request node connected to the Schedule Trigger. Configure it as a POST to DeepSeek's chat completions endpoint. The critical part: explicitly ask for JSON output in your prompt so the next node can parse it reliably.
// URL: https://api.deepseek.com/v1/chat/completions
// Method: POST
// Header: Authorization: Bearer YOUR_DEEPSEEK_API_KEY
// Body (JSON):
{
"model": "deepseek-chat",
"messages": [
{
"role": "system",
"content": "You are an SEO content strategist. Respond ONLY with a valid JSON array. No markdown, no backticks, no explanation — just the raw JSON array."
},
{
"role": "user",
"content": "Generate 5 blog post topics about n8n workflow automation. Return a JSON array where each item has: title (string), keyword (string), outline (array of 4-5 H2 heading strings). Example format: [{\"title\": \"...\", \"keyword\": \"...\", \"outline\": [\"...\", \"...\"]}]"
}
],
"temperature": 0.8,
"max_tokens": 2000
}
Now add a Code node to parse the response. This version handles the cases where DeepSeek wraps output in markdown fences despite being asked not to:
// Parse topics from DeepSeek response with error handling
const raw = $input.item.json.choices[0].message.content;
// Strip markdown fences if present (```json ... ```)
const cleaned = raw.replace(/```json\s*/gi, '').replace(/```\s*/g, '').trim();
let topics;
try {
topics = JSON.parse(cleaned);
} catch (e) {
// Fallback: try extracting a JSON array from anywhere in the string
const match = cleaned.match(/\[[\s\S]*\]/);
if (!match) {
throw new Error('DeepSeek did not return parseable JSON. Raw response: ' + raw.substring(0, 300));
}
topics = JSON.parse(match[0]);
}
// Validate structure
if (!Array.isArray(topics) || topics.length === 0) {
throw new Error('Parsed result is not a non-empty array. Got: ' + JSON.stringify(topics));
}
return topics.map(t => ({ json: t }));
Write Full Articles with DeepSeek
Add a Split In Batches node (batch size: 1, max iterations: 5). This processes one topic at a time through the rest of the pipeline. Then add a second HTTP Request node to write the full article:
// URL: https://api.deepseek.com/v1/chat/completions
// Method: POST
// Header: Authorization: Bearer YOUR_DEEPSEEK_API_KEY
// Body (JSON) — use n8n expressions for {{ topic fields }}:
{
"model": "deepseek-chat",
"messages": [
{
"role": "system",
"content": "You are a technical blog writer specialising in workflow automation. Write clear, practical content that helps developers solve real problems. Use H2 and H3 HTML tags (not markdown). Return only the article HTML body — no , no , no tags."
},
{
"role": "user",
"content": "Write a 1,200-word SEO article with this structure:\n\nTitle: {{ $json.title }}\nTarget keyword: {{ $json.keyword }}\nOutline (use these as H2 headings): {{ $json.outline.join(', ') }}\n\nRequirements:\n- Use the target keyword 3-4 times naturally\n- Start with a practical intro (no fluff)\n- Each section should include at least one concrete example or code snippet\n- End with a short conclusion and next steps\n- Return only HTML (p, h2, h3, ul, li, pre, code tags)"
}
],
"temperature": 0.7,
"max_tokens": 3000
}
// After this, add a Code node to extract the article:
// const content = $input.item.json.choices[0].message.content;
// return { json: { article_html: content, title: $input.item.json.title, keyword: $input.item.json.keyword } };
Generate Featured Images with Gemini Imagen 3
gemini-pro-vision for image generation. That model analyses images — it doesn't create them. For generation, use the correct endpoint: imagen-3.0-generate-001.
// URL: https://generativelanguage.googleapis.com/v1beta/models/imagen-3.0-generate-001:predict?key=YOUR_GEMINI_API_KEY
// Method: POST
// Body (JSON):
{
"instances": [
{
"prompt": "Professional blog thumbnail for a technical article titled '{{ $json.title }}'. Style: flat vector illustration, dark background (#1E1C18), gold accent lines (#C9920A), no text, no watermark. Clean, modern, and relevant to workflow automation."
}
],
"parameters": {
"sampleCount": 1,
"aspectRatio": "16:9",
"safetyFilterLevel": "BLOCK_MEDIUM_AND_ABOVE",
"personGeneration": "DONT_ALLOW"
}
}
// The response contains base64 image data:
// $json.predictions[0].bytesBase64Encoded
// Upload this to Blogger as the post's featured image
Auto-Publish to Blogger via API
Add a final HTTP Request node to publish the post. You'll need your Blog ID (visible in the Blogger dashboard URL) and a valid OAuth 2.0 access token (see FAQ for how to get one).
// URL: https://www.googleapis.com/blogger/v3/blogs/YOUR_BLOG_ID/posts
// Method: POST
// Header: Authorization: Bearer YOUR_OAUTH_ACCESS_TOKEN
// Query param: isDraft=false (set to true to review before publishing)
// Body (JSON):
{
"kind": "blogger#post",
"title": "{{ $json.title }}",
"content": "{{ $json.article_html }}",
"labels": ["n8n", "AI Automation", "Workflow Tutorial"]
}
// After this node, add the Wait node:
// Type: n8n-nodes-base.wait
// Amount: 3
// Unit: seconds
// This respects Blogger API rate limits (300 write requests per minute per user)
6. Complete Workflow JSON — Download & Import
This is the full importable n8n workflow. Replace the four placeholder values before activating: YOUR_DEEPSEEK_API_KEY, YOUR_GEMINI_API_KEY, YOUR_BLOG_ID, and YOUR_OAUTH_ACCESS_TOKEN.
To import: open n8n → Workflows → top-right menu → Import from File (or paste JSON directly).
{
"name": "AI Content Factory — DeepSeek + Gemini Imagen + Blogger",
"nodes": [
{
"parameters": {
"rule": {
"interval": [{ "field": "cronExpression", "expression": "0 9 * * 1" }]
}
},
"id": "node-schedule",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.1,
"position": [240, 300]
},
{
"parameters": {
"method": "POST",
"url": "https://api.deepseek.com/v1/chat/completions",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{ "name": "Authorization", "value": "Bearer YOUR_DEEPSEEK_API_KEY" },
{ "name": "Content-Type", "value": "application/json" }
]
},
"sendBody": true,
"contentType": "json",
"body": {
"model": "deepseek-chat",
"messages": [
{
"role": "system",
"content": "You are an SEO content strategist. Respond ONLY with a valid JSON array. No markdown, no backticks, no explanation."
},
{
"role": "user",
"content": "Generate 5 blog post topics about n8n workflow automation. Return a JSON array where each item has: title (string), keyword (string), outline (array of 4-5 H2 heading strings)."
}
],
"temperature": 0.8,
"max_tokens": 2000
}
},
"id": "node-deepseek-topics",
"name": "DeepSeek — Generate Topics",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [440, 300]
},
{
"parameters": {
"jsCode": "const raw = $input.item.json.choices[0].message.content;\nconst cleaned = raw.replace(/```json\\s*/gi, '').replace(/```\\s*/g, '').trim();\nlet topics;\ntry {\n topics = JSON.parse(cleaned);\n} catch (e) {\n const match = cleaned.match(/\\[[\\s\\S]*\\]/);\n if (!match) throw new Error('Cannot parse topics: ' + raw.substring(0, 200));\n topics = JSON.parse(match[0]);\n}\nif (!Array.isArray(topics) || topics.length === 0) throw new Error('Empty topics array');\nreturn topics.map(t => ({ json: t }));"
},
"id": "node-parse-topics",
"name": "Parse Topics",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [640, 300]
},
{
"parameters": {
"batchSize": 1,
"options": { "reset": false }
},
"id": "node-split",
"name": "Split In Batches",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [840, 300]
},
{
"parameters": {
"method": "POST",
"url": "https://api.deepseek.com/v1/chat/completions",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{ "name": "Authorization", "value": "Bearer YOUR_DEEPSEEK_API_KEY" },
{ "name": "Content-Type", "value": "application/json" }
]
},
"sendBody": true,
"contentType": "json",
"body": {
"model": "deepseek-chat",
"messages": [
{
"role": "system",
"content": "You are a technical blog writer for workflow automation. Return only HTML body content using p, h2, h3, ul, li, pre, code tags. No or wrapper."
},
{
"role": "user",
"content": "=concat(\"Write a 1200-word SEO article. Title: \", $json.title, \". Keyword: \", $json.keyword, \". Outline: \", $json.outline)"
}
],
"temperature": 0.7,
"max_tokens": 3000
}
},
"id": "node-deepseek-article",
"name": "DeepSeek — Write Article",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [1040, 300]
},
{
"parameters": {
"jsCode": "const articleHtml = $input.item.json.choices[0].message.content;\nconst title = $('Split In Batches').item.json.title;\nconst keyword = $('Split In Batches').item.json.keyword;\nreturn { json: { article_html: articleHtml, title, keyword } };"
},
"id": "node-extract-article",
"name": "Extract Article",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1240, 300]
},
{
"parameters": {
"method": "POST",
"url": "=concat(\"https://generativelanguage.googleapis.com/v1beta/models/imagen-3.0-generate-001:predict?key=YOUR_GEMINI_API_KEY\")",
"sendBody": true,
"contentType": "json",
"body": {
"instances": [
{
"prompt": "=concat(\"Professional blog thumbnail for: '\", $json.title, \"'. Flat vector, dark background, gold accents, no text, workflow automation theme.\")"
}
],
"parameters": {
"sampleCount": 1,
"aspectRatio": "16:9",
"safetyFilterLevel": "BLOCK_MEDIUM_AND_ABOVE",
"personGeneration": "DONT_ALLOW"
}
}
},
"id": "node-gemini-imagen",
"name": "Gemini Imagen 3 — Generate Thumbnail",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [1440, 300]
},
{
"parameters": {
"method": "POST",
"url": "https://www.googleapis.com/blogger/v3/blogs/YOUR_BLOG_ID/posts",
"sendQuery": true,
"queryParameters": {
"parameters": [{ "name": "isDraft", "value": "false" }]
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{ "name": "Authorization", "value": "Bearer YOUR_OAUTH_ACCESS_TOKEN" },
{ "name": "Content-Type", "value": "application/json" }
]
},
"sendBody": true,
"contentType": "json",
"body": {
"kind": "blogger#post",
"title": "={{ $('Extract Article').item.json.title }}",
"content": "={{ $('Extract Article').item.json.article_html }}",
"labels": ["n8n", "AI Automation", "Workflow Tutorial"]
}
},
"id": "node-blogger",
"name": "Blogger — Publish Post",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [1640, 300]
},
{
"parameters": {
"unit": "seconds",
"amount": 3
},
"id": "node-wait",
"name": "Wait 3s",
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [1840, 300],
"webhookId": "wait-webhook-id"
}
],
"connections": {
"Schedule Trigger": { "main": [[{ "node": "DeepSeek — Generate Topics", "type": "main", "index": 0 }]] },
"DeepSeek — Generate Topics": { "main": [[{ "node": "Parse Topics", "type": "main", "index": 0 }]] },
"Parse Topics": { "main": [[{ "node": "Split In Batches", "type": "main", "index": 0 }]] },
"Split In Batches": { "main": [[{ "node": "DeepSeek — Write Article", "type": "main", "index": 0 }]] },
"DeepSeek — Write Article": { "main": [[{ "node": "Extract Article", "type": "main", "index": 0 }]] },
"Extract Article": { "main": [[{ "node": "Gemini Imagen 3 — Generate Thumbnail", "type": "main", "index": 0 }]] },
"Gemini Imagen 3 — Generate Thumbnail": { "main": [[{ "node": "Blogger — Publish Post", "type": "main", "index": 0 }]] },
"Blogger — Publish Post": { "main": [[{ "node": "Wait 3s", "type": "main", "index": 0 }]] },
"Wait 3s": { "main": [[{ "node": "Split In Batches", "type": "main", "index": 0 }]] }
},
"active": false,
"settings": {
"executionOrder": "v1",
"saveManualExecutions": true,
"callerPolicy": "workflowsFromSameOwner"
},
"tags": ["content-automation", "ai", "blogger", "deepseek", "gemini"]
}
7. Common Errors and How to Fix Them
The free tier has minute-level rate limits. Increase the Wait node to 5 seconds between requests, or add a second Wait node before the first DeepSeek call. For persistent rate-limit issues, see the Workflow Execution Failed guide — it covers retry logic setup.
DeepSeek occasionally wraps output in markdown fences despite being asked not to. The Code node above already handles this with a regex fallback. If it still fails, check the raw response by adding a Debug Helper node after the DeepSeek call and inspect
choices[0].message.content.
You're likely using
gemini-pro-vision or gemini-1.5-pro in the URL. Change it to imagen-3.0-generate-001. Also confirm your Gemini API key has Imagen access enabled in Google AI Studio.
OAuth access tokens expire after 1 hour. You need to implement token refresh using your refresh token, or regenerate the access token manually for testing. A full OAuth refresh automation tutorial is coming — subscribe to stay notified.
Increase the HTTP Request timeout under each node's Options → Timeout (ms) to 120,000 (120 seconds). For self-hosted n8n, also increase
EXECUTIONS_TIMEOUT in your environment config. The localhost fix guide includes VPS environment configuration tips.
8. Real Cost Breakdown — Per 100 Articles
| Service | Usage per 100 articles | Cost |
|---|---|---|
| DeepSeek (topics + articles) | ~220,000 tokens | $0.031 |
| Gemini Imagen 3 | 100 image requests | $0 (free tier) |
| n8n Cloud (free tier) | ~500 executions | $0 (within limit) |
| n8n self-hosted (if needed) | VPS $5/month | $5/month flat |
| Total (cloud, free tiers) | 100 complete articles | ~$0.03 |
For comparison: a freelance writer charges $15–$80 per article. GPT-4o costs roughly $0.60–$1.20 per article at this length. DeepSeek brings that down by 95%+ for technical how-to content, where factual accuracy matters more than prose style.
9. Advanced Tips to Scale
- Niche-specific prompts — Hardcode your niche in the system prompt instead of leaving it generic. "n8n automation for e-commerce" produces much tighter articles than "workflow automation."
- Add internal link injection — After the Extract Article node, add a Code node that replaces target phrases with links to your existing posts automatically.
- Route to WordPress instead of Blogger — Replace the Blogger API call with a WordPress REST API POST to
/wp-json/wp/v2/posts. The body structure is almost identical. - Schedule drafts, not live posts — Set
isDraft=truein the Blogger call and review each article before it goes live. This adds 10 minutes of work per article but removes the risk of publishing AI errors. - Add a Google Sheets log node — After the Blogger node, log the post URL, title, and timestamp to a sheet. This becomes your content calendar automatically.
10. Frequently Asked Questions
Yes. Replace the DeepSeek endpoint with https://api.openai.com/v1/chat/completions and use gpt-3.5-turbo (budget) or gpt-4o-mini (better quality). The request body format is identical — DeepSeek was designed to be API-compatible with OpenAI. Costs will be 20–30x higher per article, but both options work within the same workflow.
1. Go to console.cloud.google.com and create a new project.
2. Enable Blogger API v3 in the API Library.
3. Go to Credentials → Create Credentials → OAuth 2.0 Client ID. Choose Desktop app as the type.
4. Download the credentials JSON, then use Google OAuth Playground to generate an access token and refresh token for the Blogger API scope.
5. Store both tokens in n8n under Credentials → New → HTTP Header Auth. The access token expires hourly — a full OAuth auto-refresh tutorial is coming soon.
Google's policy targets low-quality content, not AI content. A well-structured, accurate article that genuinely helps readers will not be penalised regardless of how it was drafted. What does get penalised is scaled, unreviewed content that's thin, repetitive, or inaccurate. Use this workflow to generate first drafts, then spend 10–15 minutes per article editing for accuracy, adding your own experience, and ensuring it actually solves the reader's problem.
Absolutely — and it's recommended for production use since the cloud free tier has a 2,500 executions/month limit. For self-hosted setups, make sure your instance is publicly accessible (not returning localhost in webhook URLs — see the localhost fix guide) and set NODE_OPTIONS=--max-old-space-size=2048 in your environment to handle larger article payloads without memory errors.
11. Conclusion
You now have a working AI content pipeline: topic generation, article writing, image creation, and auto-publishing — all connected inside n8n, running for less than $0.03 per article.
The next steps, in order:
- Download and import the JSON above.
- Get your API keys — DeepSeek and Google AI Studio both offer free credits to start.
- Test with
isDraft=trueon the Blogger call first — review the output before going live. - Edit before publishing — add real examples from your own experience, verify any technical claims, and make it yours.