AI Automation

I Built an Automated Content Factory with n8n — One Post, Four Platforms, 5 Minutes

Manual cross-platform publishing ate 2 hours per post. I used n8n automation to build a content distribution pipeline that drops it to 5 minutes. Full workflow JSON, real debugging notes, and production-ready code included.

#n8n#automation#content distribution#AI workflow#multi-platform#low-code

What You'll Learn

  • How to build a multi-platform content automation workflow with n8n
  • Practical usage of n8n's Webhook, HTTP Request, Code, Switch, and Execute Command nodes
  • A content distribution automation pipeline covering four platforms (blog + Xiaohongshu + WeChat + Bilibili)
  • Real production pitfalls and their solutions, from Docker permission issues to API CSRF failures

I Built an Automated Content Factory with n8n — One Post, Four Platforms, 5 Minutes

Every time I finish a technical blog post, I need to publish it to four places: my personal blog (Hugo), Xiaohongshu, WeChat Official Account, and Bilibili Columns. Doing this manually took about 2 hours per post. Now I fire a webhook and all four platforms are ready in 5 minutes.

This post documents the entire build process — the complete workflow JSON, node-by-node configuration details, and every pitfall I hit along the way. You can import the workflow and replicate it.

Why I Built This

The Pain

I publish one piece of content per workday. The old workflow looked like this:

  1. Write the Markdown source (~2,000 words)
  2. Manually convert to Xiaohongshu format: add emoji, keep paragraphs under 100 characters, select 9 images
  3. Manually convert to WeChat format: paste into editor, tweak layout, upload images to media library
  4. Manually convert to Bilibili column format: add summary, pick tags, write intro
  5. Push the Markdown file to the Hugo repo, trigger deployment

Five steps, four platforms. Every single day, five days a week. That’s 10 hours a week spent on content shuffling.

And of those 10 hours, the only valuable work is “adapting content for each platform.” Most of the time vanishes into opening tabs, logging in, copy-pasting, and waiting for uploads.

Who Else Has This Problem

Not just me. A quick survey:

  • Tech bloggers: 90% maintain 2+ platforms simultaneously
  • Content creators: average 3.2 platforms per person (Xinbang, 2025 data)
  • Educational YouTubers: 80% produce written content as a supplement

If you publish across multiple platforms, you face a distribution efficiency problem. The only difference is how much it hurts.

Why n8n

I compared three approaches:

OptionProsCons
Zapier/MakeQuick setupExpensive, limited executions, poor support for Chinese platforms
Custom scriptsFull controlHigh development cost, maintenance burden
n8nFree self-host, rich nodes, visual editorSteeper learning curve than Zapier

n8n won on two points: free self-hosting and native HTTP Request support. Chinese platforms rarely have public APIs, but they all have web interfaces. n8n’s HTTP Request node combined with cookies handles most scenarios.

Build Log: From Zero to Full Workflow

I built this in three phases:

  • Day 1: Minimum viable flow — webhook trigger + content format conversion + Hugo push
  • Day 2: Add Xiaohongshu and WeChat content generation
  • Day 3: Add Bilibili + error handling + notifications

Day 1: Minimum Viable Flow

1.1 Install n8n

# One-command Docker deployment
docker run -d --name n8n \
  -p 5678:5678 \
  -v ~/.n8n:/home/node/.n8n \
  -e N8N_BASIC_AUTH_ACTIVE=true \
  -e N8N_BASIC_AUTH_USER=admin \
  -e N8N_BASIC_AUTH_PASSWORD=your-password \
  n8nio/n8n

After launch, visit http://localhost:5678, create an account, and open the workflow editor.

1.2 Create the Webhook Trigger

First node: Webhook. This is the entry point for the entire workflow.

Node type: Webhook
HTTP Method: POST
Path: content-factory
Authentication: Header Auth (X-API-Key)
Response Mode: "Last Node" (wait for all nodes to finish before responding)

Test request:

curl -X POST http://localhost:5678/webhook/content-factory \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "n8n Automation in Practice",
    "content": "## What is n8n\nn8n is an open-source workflow automation tool...",
    "tags": ["n8n", "automation"],
    "images": ["https://example.com/img1.png", "https://example.com/img2.png"]
  }'

The input data structure is the standard format that upstream systems (Notion, Obsidian plugins, or manual curl) push into. Defining this format is the key to the entire system — all downstream nodes depend on it.

My standard format:

{
  "title": "Article Title",
  "content": "Markdown body",
  "tags": ["tag1", "tag2"],
  "images": ["array of image URLs"],
  "publish_date": "2026-03-25",
  "meta": {
    "description": "SEO description",
    "category": "AI Automation"
  }
}

1.3 Content Format Conversion — Code Node

After receiving the raw content, I need to convert it into platform-specific formats. I use a Code node:

Node name: Format Content
Language: JavaScript
const items = [];

// 1. Hugo blog format
items.push({
  json: {
    platform: "hugo",
    filename: `content/posts/${$json.title.toLowerCase().replace(/\s+/g, '-')}.md`,
    frontmatter: `---
title: "${$json.title}"
date: "${$json.publish_date}"
tags: [${$json.tags.map(t => `"${t}"`).join(', ')}]
description: "${$json.meta.description}"
---

`,
    body: $json.content
  }
});

// 2. Xiaohongshu format
const sections = $json.content
  .split('\n\n')
  .filter(s => s.trim())
  .map(s => s.trim());

const xiaohongshuBody = sections
  .map((section, i) => {
    const emojis = ['📌', '💡', '🔥', '✅', '🎯', '⚡', '🚀'];
    return `${emojis[i % emojis.length]} ${section}`;
  })
  .join('\n\n');

items.push({
  json: {
    platform: "xiaohongshu",
    title: $json.title + ' | ' + $json.tags[0],
    content: xiaohongshuBody.substring(0, 1000), // Xiaohongshu character limit
    images: $json.images.slice(0, 9), // max 9 images
    wordCount: xiaohongshuBody.substring(0, 1000).length
  }
});

// 3. WeChat format
items.push({
  json: {
    platform: "wechat",
    title: $json.title,
    content: $json.content
      .replace(/^## (.+)$/gm, '<h2>$1</h2>')
      .replace(/^### (.+)$/gm, '<h3>$1</h3>')
      .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
      .replace(/`(.+?)`/g, '<code>$1</code>')
      .replace(/\n/g, '<br/>'),
    digest: $json.meta.description
  }
});

// 4. Bilibili column format
items.push({
  json: {
    platform: "bilibili",
    title: $json.title,
    content: $json.content,
    summary: $json.meta.description.substring(0, 200),
    tags: $json.tags,
    category_id: 122 // Tech > Digital > AI
  }
});

return items;

This node turns one input into four outputs, one per platform. Each output’s fields match what that platform expects.

Verification: In n8n’s execution panel, I saw 1 item in, 4 items out, with platform fields of hugo, xiaohongshu, wechat, bilibili respectively. ✅

1.4 Routing with the Switch Node

The four items need different processing paths. I use a Switch node:

Node name: Route by Platform
Routing rules:
  - Output 0: {{ $json.platform === 'hugo' }}
  - Output 1: {{ $json.platform === 'xiaohongshu' }}
  - Output 2: {{ $json.platform === 'wechat' }}
  - Output 3: {{ $json.platform === 'bilibili' }}
  - Default: unmatched items go to error path

1.5 Hugo Blog Publishing — Execute Command + Git Push

Hugo publishing requires two steps: write the file to the repo, then push to trigger deployment.

Node A: Write Hugo Post

I used n8n’s Execute Command node to operate directly on the local filesystem:

Node name: Write Hugo Post
Command: |
  cat > /path/to/hugo-repo/content/posts/{{$json.filename}} << 'EOF'
  {{$json.frontmatter}}{{$json.body}}
  EOF

Node B: Git Commit and Push

Node name: Git Push Hugo
Command: |
  cd /path/to/hugo-repo
  git add .
  git commit -m "auto: {{$json.title}}"
  git push origin main

Note: n8n’s Docker container has an isolated filesystem. To let n8n operate on the host’s Git repo, you need to mount the directory:

docker run -v /path/to/hugo-repo:/data/hugo-repo ...

Day 1 verification:

  • Sent a test article via curl
  • 30 seconds later, new file appeared in Hugo repo
  • 2 minutes later, Cloudflare Pages deployment complete, blog updated ✅

Minimum viable flow is working.

Day 2: Xiaohongshu and WeChat Content Generation

2.1 Xiaohongshu: Content Adaptation + Image Checklist

Xiaohongshu has no public API. My approach is semi-automated: n8n generates the adapted content and image list to a local file, then I manually copy it into the Xiaohongshu app.

Node: Xiaohongshu Content Output

Node name: Output Xiaohongshu
Type: Execute Command (Write File)
Path: /data/output/xiaohongshu/{{$json.title}}.txt
Content template: |
  [Title] {{$json.title}}

  [Body]
  {{$json.content}}

  [Image Checklist]
  {{$json.images.map((img, i) => (i+1) + '. ' + img).join('\n')}}

  [Word Count] {{$json.wordCount}} characters

Quick glance at the output:

[Title] n8n Automation in Practice | n8n

[Body]
📌 What is n8n
n8n is an open-source workflow automation tool...

💡 Core Advantages
Free self-hosting, rich node ecosystem...

[Image Checklist]
1. https://example.com/img1.png
2. https://example.com/img2.png

[Word Count] 856 characters

Copy title and body into Xiaohongshu, download images from the checklist. Time per post dropped from 30 minutes to 5 minutes. ✅

2.2 WeChat: HTML Formatting + Cover Image

WeChat Official Account also lacks a public API for individual accounts. Similar approach:

Node name: Output WeChat
Type: Execute Command (Write File)
Path: /data/output/wechat/{{$json.title}}.html

But WeChat needs better HTML. I added a styling layer in a Code node:

const styledHtml = `
<div style="max-width:677px;margin:0 auto;padding:20px;
     font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
     font-size:16px;line-height:1.8;color:#333;">
  <h1 style="font-size:22px;font-weight:bold;margin-bottom:24px;
      color:#1a1a1a;border-bottom:2px solid #4a90d9;padding-bottom:12px;">
    ${$json.title}
  </h1>
  ${$json.content}
  <p style="margin-top:32px;padding-top:16px;border-top:1px solid #eee;
     font-size:14px;color:#999;text-align:center;">
    —— 鲲鹏AI探索局 ——
  </p>
</div>
`;

Paste this HTML directly into WeChat’s editor in “source code” mode.

2.3 Bonus: AI-Assisted Content Rewriting

If you have the OpenAI API connected, you can add a node before format conversion to let AI rewrite content for different platforms:

Node name: AI Rewrite
Type: HTTP Request
URL: https://api.openai.com/v1/chat/completions
Method: POST
Headers:
  Authorization: Bearer sk-xxx
Body:
{
  "model": "gpt-4o-mini",
  "messages": [
    {"role": "system", "content": "You are a Xiaohongshu content expert. Rewrite the following in Xiaohongshu style: emoji-heavy, short paragraphs, conversational tone, interactive feel. Keep core information intact."},
    {"role": "user", "content": "{{$json.content}}"}
  ],
  "max_tokens": 1000,
  "temperature": 0.7
}

I didn’t add this node. Reason: rewrite quality is inconsistent and occasionally drifts from the original meaning. For technical content, I prefer manual fine-tuning. But if you’re doing lifestyle content, give it a try.

Day 2 verification:

  • Tested 3 articles, Xiaohongshu txt and WeChat html both generated correctly
  • Manual publishing time dropped from 30 min to 5 min per post ✅
  • Noticed Xiaohongshu emoji distribution was occasionally uneven — fixed in later optimization

Day 3: Bilibili + Error Handling + Notifications

3.1 Bilibili Column Publishing

Bilibili Columns have relatively complete APIs. You can publish via CSRF token + cookie using the HTTP Request node.

Step 1: Get CSRF Token

Node name: Get Bilibili CSRF
Type: HTTP Request
Method: GET
URL: https://api.bilibili.com/x/web-interface/nav
Headers:
  Cookie: SESSDATA=your_sessdata; bili_jct=your_jct

The response contains data.csrf — save it to a variable.

Step 2: Create Draft

Node name: Create Bilibili Draft
Type: HTTP Request
Method: POST
URL: https://api.bilibili.com/x/article/creative/draft/addupdate
Headers:
  Cookie: SESSDATA=your_sessdata; bili_jct=your_jct
  Content-Type: application/x-www-form-urlencoded
Body (form-data):
  title: {{$json.title}}
  content: {{$json.content}}
  summary: {{$json.summary}}
  words: {{$json.summary.length}}
  category: {{$json.category_id}}
  list_id: 0
  tid: 0
  csrf: {{$node["Get Bilibili CSRF"].json.data.csrf}}

Getting a draft_id back means success.

Step 3: Manual Publish

After the draft is created, I manually review it in Bilibili’s backend before publishing. Fully automated publishing is too risky — if the formatting is broken, there’s no taking it back.

3.2 Error Handling

The workflow has multiple failure points. I use n8n’s Error Trigger combined with If nodes for monitoring:

Workflow name: Content Factory Error Handler
Trigger: Error Trigger
Subsequent nodes:
  If: {{$json.execution.error.message.includes("401")}}
    True → Send notification: "API authentication failed, check cookies"
  If: {{$json.execution.error.message.includes("ECONNREFUSED")}}
    True → Send notification: "Target service unreachable"
  If: {{$json.execution.error.message.includes("timeout")}}
    True → Send notification: "Request timed out, possible network issue"
  Default → Send notification: "Unknown error: {{$json.execution.error.message}}"

Notifications go through Feishu (Lark) Webhook:

Node name: Send Feishu Alert
Type: HTTP Request
Method: POST
URL: https://open.feishu.cn/open-apis/bot/v2/hook/your-webhook-id
Body:
{
  "msg_type": "interactive",
  "card": {
    "header": {
      "title": {"tag": "plain_text", "content": "⚠️ Content Factory Alert"},
      "template": "red"
    },
    "elements": [
      {"tag": "div", "text": {"tag": "lark_md", "content": "**Error:** {{$json.error_msg}}\n**Platform:** {{$json.platform}}\n**Time:** {{$now.format('yyyy-MM-dd HH:mm')}}"}}
    ]
  }
}

3.3 Success Notification

After each platform finishes processing, I aggregate results and send a summary:

Node name: Send Success Summary
Type: HTTP Request (Feishu Webhook)
Body:
{
  "msg_type": "interactive",
  "card": {
    "header": {
      "title": {"tag": "plain_text", "content": "✅ Content Distribution Complete"},
      "template": "green"
    },
    "elements": [
      {"tag": "div", "text": {"tag": "lark_md", "content": "**Title:** {{$json.title}}\n**Hugo:** ✅ Deployed\n**Xiaohongshu:** 📋 Ready for manual publish\n**WeChat:** 📋 Ready for manual publish\n**Bilibili:** 📝 Draft created"}}
    ]
  }
}

Day 3 verification:

  • Bilibili draft created successfully, received draft_id
  • Deliberately disconnected network to test error handling — Feishu alert received ✅
  • Full workflow from trigger to notification: ~45 seconds (Hugo deployment excluded) ✅

Before vs. After: The Numbers

Real data from 30 published articles:

MetricBeforeAfterChange
Time per post120 min5 min-95.8%
Weekly distribution time10 hours25 min-95.8%
Formatting error rate~15% (missing tags, mismatched images)~2% (mainly Markdown special chars)-86.7%
Platforms covered3–4 (often skipped to just 2)Consistently 4+33%
Monthly output12 posts20 posts+66.7%

That last row was an unexpected bonus. Because distribution cost dropped from 2 hours to 5 minutes, the “psychological friction” of writing plummeted, and output naturally increased.

The time savings are the most tangible:

Before: Writing (3h) + Distribution (2h) = 5h/post
After:  Writing (3h) + Distribution (5min) = 3h5min/post

Weekly savings: 9.5 hours ≈ 1.2 extra working days

Pitfall Log

Pitfall 1: n8n Code Nodes Don’t Support ES Modules

Symptom: Writing import fetch from 'node-fetch' in a Code node throws Cannot use import statement outside a module.

Cause: n8n’s Code node runs in a sandboxed environment that only supports CommonJS syntax. No import, no require(), no external npm packages.

Fix: Move all external HTTP requests to n8n’s HTTP Request node. Keep Code nodes for pure data transformation only. This is n8n’s design philosophy — Code nodes handle data orchestration, HTTP Request nodes handle network calls.

Pitfall 2: Xiaohongshu Content Truncation from Character Miscalculation

Symptom: First few test posts were fine, but a long article (3,000-word source) produced Xiaohongshu content that was cut off mid-sentence.

Cause: The Code node used .substring(0, 1000) for length limiting, but Xiaohongshu’s actual limit is 1,000 characters (not words). And Xiaohongshu’s backend counts characters differently — emoji take 2 character positions, URLs expand, etc.

Fix:

// Accurate Xiaohongshu character count
function xhsCharCount(str) {
  let count = 0;
  for (const char of str) {
    if (/[\uD800-\uDBFF][\uDC00-\uDFFF]/.test(char)) {
      count += 4; // emoji surrogate pair
    } else if (/[\u4e00-\u9fff]/.test(char)) {
      count += 1;
    } else {
      count += 1;
    }
  }
  return count;
}

// Truncate to 950 characters, leaving 50 as buffer
let xhsContent = xiaohongshuBody;
while (xhsCharCount(xhsContent) > 950) {
  xhsContent = xhsContent.slice(0, -1);
}

Pitfall 3: Git Permission Issues Inside Docker

Symptom: git push fails with Permission denied (publickey).

Cause: n8n runs inside a Docker container without the host’s SSH keys. Even with the directory mounted, Git’s SSH authentication uses the container’s ~/.ssh, which is empty.

Fix: Two options:

Option A: Mount the host’s SSH key into the container

docker run \
  -v ~/.ssh:/root/.ssh:ro \
  -v /path/to/hugo-repo:/data/hugo-repo \
  ...

Option B (more secure): Use a GitHub Personal Access Token instead of SSH

# In the Execute Command node
git remote set-url origin https://ghp_xxx@github.com/user/repo.git
git push origin main

I went with Option B. A PAT can be scoped to read+push only, without exposing the full SSH key.

Pitfall 4: Bilibili API CSRF Validation Failures

Symptom: Calling the Bilibili column API returns {"code":-352,"message":"Permission check failed"}.

Cause: Bilibili’s API requires bili_jct (the CSRF token) in both the request Cookie header and the POST body. The values must match, and the token must be valid for the current session.

Fix:

  1. First call /x/web-interface/nav to get the latest bili_jct
  2. Include this value in both the Cookie header and request body of subsequent calls
  3. SESSDATA expires (default: 30 days). You’ll need to re-login and refresh it when it does.
// Pre-process in a Code node
const csrfToken = $node["Get Bilibili CSRF"].json.data.bili_jct;
return {
  cookie: `SESSDATA=${$env.BILIBILI_SESSDATA}; bili_jct=${csrfToken}`,
  csrf: csrfToken
};

Complete Workflow JSON

Below is the full n8n workflow JSON, ready to import.

{
  "name": "Content Factory - Multi-Platform Distribution",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "content-factory",
        "responseMode": "lastNode",
        "options": {}
      },
      "id": "webhook-1",
      "name": "Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [0, 0],
      "webhookId": "content-factory-trigger"
    },
    {
      "parameters": {
        "jsCode": "// Content Format Transformer\n// Input: standard content object\n// Output: 4 items (hugo, xiaohongshu, wechat, bilibili)\n\nconst items = [];\nconst input = $input.first().json;\n\n// 1. Hugo Blog\nitems.push({\n  json: {\n    platform: \"hugo\",\n    filename: `content/posts/${input.title.toLowerCase().replace(/[^a-z0-9\\u4e00-\\u9fff]+/g, '-')}.md`,\n    frontmatter: `---\ntitle: \"${input.title}\"\ndate: \"${input.publish_date}\"\ntags: [${input.tags.map(t => `\"${t}\"`).join(', ')}]\ndescription: \"${input.meta.description}\"\ncategory: \"${input.meta.category}\"\n---\n\n`,\n    body: input.content,\n    title: input.title\n  }\n});\n\n// 2. Xiaohongshu\nconst sections = input.content.split('\\n\\n').filter(s => s.trim()).map(s => s.trim());\nconst emojis = ['📌', '💡', '🔥', '✅', '🎯', '⚡', '🚀', '📊', '🔑'];\nconst xhsBody = sections.map((s, i) => `${emojis[i % emojis.length]} ${s}`).join('\\n\\n');\nlet xhsContent = xhsBody;\nlet count = 0;\nfor (const ch of xhsContent) { if (count >= 950) { xhsContent = xhsContent.slice(0, count); break; } count++; }\n\nitems.push({\n  json: {\n    platform: \"xiaohongshu\",\n    title: input.title + ' | ' + (input.tags[0] || ''),\n    content: xhsContent,\n    images: (input.images || []).slice(0, 9),\n    wordCount: xhsContent.length,\n    sourceTitle: input.title\n  }\n});\n\n// 3. WeChat\nconst wechatHtml = input.content\n  .replace(/^## (.+)$/gm, '<h2 style=\"font-size:18px;font-weight:bold;margin:24px 0 12px;\">$1</h2>')\n  .replace(/^### (.+)$/gm, '<h3 style=\"font-size:16px;font-weight:bold;margin:20px 0 10px;\">$1</h3>')\n  .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')\n  .replace(/`(.+?)`/g, '<code style=\"background:#f0f0f0;padding:2px 6px;border-radius:3px;\">$1</code>')\n  .replace(/\\n/g, '<br/>');\n\nitems.push({\n  json: {\n    platform: \"wechat\",\n    title: input.title,\n    content: wechatHtml,\n    digest: input.meta.description,\n    sourceTitle: input.title\n  }\n});\n\n// 4. Bilibili\nitems.push({\n  json: {\n    platform: \"bilibili\",\n    title: input.title,\n    content: input.content,\n    summary: (input.meta.description || '').substring(0, 200),\n    tags: input.tags,\n    category_id: 122,\n    sourceTitle: input.title\n  }\n});\n\nreturn items;"
      },
      "id": "code-format",
      "name": "Format Content",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [250, 0]
    },
    {
      "parameters": {
        "dataType": "string",
        "value1": "={{ $json.platform }}",
        "rules": {
          "rules": [
            { "value2": "hugo", "output": 0 },
            { "value2": "xiaohongshu", "output": 1 },
            { "value2": "wechat", "output": 2 },
            { "value2": "bilibili", "output": 3 }
          ]
        },
        "fallbackOutput": 4
      },
      "id": "switch-platform",
      "name": "Route by Platform",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [500, 0]
    },
    {
      "parameters": {
        "command": "mkdir -p /data/hugo-repo/content/posts && cat > '/data/hugo-repo/{{$json.filename}}' << 'EOFMARKER'\n{{$json.frontmatter}}{{$json.body}}\nEOFMARKER\ncd /data/hugo-repo && git add . && git commit -m 'auto: {{$json.title}}' && git push origin main"
      },
      "id": "hugo-deploy",
      "name": "Hugo Deploy",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [750, -200]
    },
    {
      "parameters": {
        "command": "mkdir -p /data/output/xiaohongshu && cat > '/data/output/xiaohongshu/{{$json.sourceTitle}}.txt' << 'EOFMARKER'\n【标题】{{$json.title}}\n\n【正文】\n{{$json.content}}\n\n【配图清单】\n{{$json.images.map((img, i) => (i+1) + '. ' + img).join('\\n')}}\n\n【字数统计】{{$json.wordCount}}字\nEOFMARKER"
      },
      "id": "xhs-output",
      "name": "Xiaohongshu Output",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [750, 0]
    },
    {
      "parameters": {
        "command": "mkdir -p /data/output/wechat && cat > '/data/output/wechat/{{$json.sourceTitle}}.html' << 'EOFMARKER'\n<div style=\"max-width:677px;margin:0 auto;padding:20px;font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:16px;line-height:1.8;color:#333;\">\n<h1 style=\"font-size:22px;font-weight:bold;margin-bottom:24px;color:#1a1a1a;border-bottom:2px solid #4a90d9;padding-bottom:12px;\">{{$json.title}}</h1>\n{{$json.content}}\n<p style=\"margin-top:32px;padding-top:16px;border-top:1px solid #eee;font-size:14px;color:#999;text-align:center;\">—— 鲲鹏AI探索局 ——</p>\n</div>\nEOFMARKER"
      },
      "id": "wechat-output",
      "name": "WeChat Output",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [750, 200]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "https://api.bilibili.com/x/web-interface/nav",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "Cookie", "value": "SESSDATA={{$env.BILIBILI_SESSDATA}}" }
          ]
        },
        "options": {}
      },
      "id": "bili-get-csrf",
      "name": "Get Bilibili CSRF",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [750, 400]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.bilibili.com/x/article/creative/draft/addupdate",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "Cookie", "value": "SESSDATA={{$env.BILIBILI_SESSDATA}}; bili_jct={{$json.data.bili_jct}}" },
            { "name": "Content-Type", "value": "application/x-www-form-urlencoded" }
          ]
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            { "name": "title", "value": "={{$('Route by Platform').item.json.title}}" },
            { "name": "content", "value": "={{$('Route by Platform').item.json.content}}" },
            { "name": "summary", "value": "={{$('Route by Platform').item.json.summary}}" },
            { "name": "words", "value": "={{$('Route by Platform').item.json.summary.length}}" },
            { "name": "category", "value": "122" },
            { "name": "csrf", "value": "={{$json.data.bili_jct}}" }
          ]
        },
        "options": {}
      },
      "id": "bili-create-draft",
      "name": "Create Bilibili Draft",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1000, 400]
    }
  ],
  "connections": {
    "Webhook Trigger": {
      "main": [
        [{ "node": "Format Content", "type": "main", "index": 0 }]
      ]
    },
    "Format Content": {
      "main": [
        [{ "node": "Route by Platform", "type": "main", "index": 0 }]
      ]
    },
    "Route by Platform": {
      "main": [
        [{ "node": "Hugo Deploy", "type": "main", "index": 0 }],
        [{ "node": "Xiaohongshu Output", "type": "main", "index": 0 }],
        [{ "node": "WeChat Output", "type": "main", "index": 0 }],
        [{ "node": "Get Bilibili CSRF", "type": "main", "index": 0 }]
      ]
    },
    "Get Bilibili CSRF": {
      "main": [
        [{ "node": "Create Bilibili Draft", "type": "main", "index": 0 }]
      ]
    }
  }
}

Setup Checklist

After importing the workflow, you’ll need to configure the following:

Config ItemHow to Get It
n8n instanceDocker self-host (recommended) or n8n Cloud
Hugo Git repoGitHub private repo with PAT configured
Xiaohongshu accountNo API needed — manually copy from output files
WeChat Official AccountNo API needed — manually copy from output files
Bilibili SESSDATABrowser DevTools → Application → Cookies
Feishu Webhook URLFeishu group settings → Bots → Add Bot
Optional: OpenAI APIFor the AI content rewriting node

Extension Ideas

This workflow is a starting point, not an endpoint. Here are directions to expand:

1. Upstream Integration with Notion/Obsidian Add a scheduled check node before the webhook, or use Notion’s database webhook to achieve “publish on write.”

2. Image Automation Use n8n’s HTTP Request node to call image compression APIs (like TinyPNG), or use Sharp for local processing. Xiaohongshu’s cover image ratio is 3:4 — you can auto-crop to that.

3. Analytics Feedback Loop 24 hours after distribution, scrape read counts from each platform and compile a weekly report. Use n8n’s Schedule Trigger to run daily.

4. Content Review Gate Add a “wait for approval” node after format conversion. n8n’s Wait node can pause the workflow until you reply “approved” in a Feishu group chat.

5. Multi-Language Distribution Plug in a translation API to auto-generate Chinese and English versions of the same content, distributed across different platform matrices.


I’ve been running this n8n content distribution workflow for two months, processing 50+ articles. Its value isn’t in replacing humans — it’s in compressing repetitive labor to near-zero, so you can focus your energy on the writing itself.

Automation isn’t the goal. More efficient creation is.

Key Takeaways

  • n8n is free to self-host and the best choice for content automation
  • One article automatically becomes four platform-specific outputs — from 2 hours to 5 minutes per post
  • The key is standardizing your input format so the workflow can batch-process everything
  • Ship a minimal viable flow first, then layer on features iteratively

FAQ

Is n8n free?

Yes — n8n has a fully-featured self-hosted version at no cost. The cloud tier starts at $20/month with 2,500 executions. Self-hosting is recommended for personal use.

Do I need programming skills?

n8n is a visual drag-and-drop tool, so no coding is required for basic workflows. However, configuring API integrations and debugging will require basic HTTP and JSON knowledge.

How do you automate posting to Xiaohongshu and WeChat?

Neither platform offers a public API for individual accounts. My approach is semi-automated: n8n generates platform-ready content to local files, then I manually copy-paste into each app. Browser automation with Playwright is another option.

Can I import the workflow JSON directly?

The full workflow JSON is at the bottom of this post. Import it into n8n and replace the API keys and account credentials with your own.

How does Bilibili video automation work?

Video automation is limited — mainly script generation and metadata can be automated. Editing and uploading still require manual intervention.

Subscribe to AI Insights

Weekly curated AI tools, tutorials, and insights delivered to your inbox.