How to Build an AI Content Factory with n8n, DeepSeek & Gemini

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.

Important before you start: This workflow is a first-draft generator. Always review AI output before publishing — add your own experience, verify facts, and make sure each article genuinely helps your readers. Google rewards editorial oversight, and so do your readers.

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
Flowchart of the AI content factory pipeline: schedule trigger, topic generation, article writing, image generation, and publishing to Blogger.
Figure 1: The complete AI content factory flow — from weekly trigger to live post.

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.
Why Gemini Imagen specifically (not gemini-pro-vision)? 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

1

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.

Schedule Trigger — cron config
// 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 * * *
Timezone tip: n8n uses the server's system timezone. If articles are publishing at the wrong time, check your instance timezone setting under Settings → n8n Settings → Timezone and match it to your local timezone.
2

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.

HTTP Request — DeepSeek topics
// 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:

Code node — Parse Topics (safe)
// 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 }));
n8n workflow nodes: Schedule Trigger connected to DeepSeek Topics HTTP Request, then to Parse Topics code node.
Figure 2: The first three nodes — schedule, topic generation, and JSON parsing.
3

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:

HTTP Request — DeepSeek write 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 } };
4

Generate Featured Images with Gemini Imagen 3

Common mistake: Using 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.
HTTP Request — Gemini Imagen 3
// 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
n8n workflow showing the complete loop: Split In Batches, DeepSeek write article, extract article, Gemini Imagen, Blogger publish, wait, and loop back.
Figure 3: The complete loop — process one topic at a time through writing, image generation, and publishing.
5

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).

HTTP Request — Blogger API publish
// 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).

ai-content-factory-n8n.json
{
  "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

DeepSeek API 429 (Rate Limit)
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.
JSON Parse Error in "Parse Topics" node
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.
Gemini 400 — Model Not Found
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.
Blogger API 401 (Unauthorized)
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.
Workflow times out mid-execution
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.

Bar chart comparing cost per article: freelance writer $50, GPT-4o $0.90, DeepSeek+Gemini $0.03.
Figure 4: Cost per article — DeepSeek + Gemini is 99.9% cheaper than freelance writing.

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=true in 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

Can I use ChatGPT instead of DeepSeek?

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.

How do I get Blogger OAuth 2.0 credentials?

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.

Will Google penalise AI-generated content?

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.

Can I run this on self-hosted n8n?

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:

  1. Download and import the JSON above.
  2. Get your API keys — DeepSeek and Google AI Studio both offer free credits to start.
  3. Test with isDraft=true on the Blogger call first — review the output before going live.
  4. Edit before publishing — add real examples from your own experience, verify any technical claims, and make it yours.
If you get stuck: Leave a comment below with the exact error message and which node it failed on. I'll respond with a specific fix. For webhook and execution errors, the troubleshooting guide covers most common cases before you even need to ask.

Post a Comment

Previous Post Next Post