This workflow corresponds to n8n.io template #16554 — we link there as the canonical source.
This workflow follows the HTTP Request → Slack recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "MvwuXWObCbkeK31N",
"meta": {
"builderVariant": "mcp",
"aiBuilderAssisted": true
},
"name": "Multi-agent AI press room to research, draft and review WordPress articles",
"tags": [
{
"id": "Fur9Q2nIpUv2w23M",
"name": "triple8labs",
"createdAt": "2026-06-07T20:22:00.276Z",
"updatedAt": "2026-06-07T20:22:00.276Z"
}
],
"nodes": [
{
"id": "ae12e37e-1377-41a4-8350-6919d13678e9",
"name": "Sticky Note 586d2dc7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-160,
-464
],
"parameters": {
"width": 1072,
"height": 1424,
"content": "## Multi-agent AI press room to research, draft and review WordPress articles\n\n### How it works\n\nSend a brief via webhook and a three-agent pipeline handles the rest. A researcher agent outlines the article and plans a featured image. A writer agent drafts the full HTML post. A reviewer agent edits for voice, structure and factual accuracy. Imagen 4 generates a 16:9 featured image. The result is published as a WordPress draft with Rank Math SEO fields set, and a Slack notification is sent with a link to the draft for review.\n\nEach agent can use a different LLM provider \u2014 mix Claude, Gemini, Mistral or OpenAI per stage in the CONFIG node.\n\n**Activate the workflow** then POST to `/webhook/article-publisher`:\n```\n{ \"prompt\": \"Write an article about...\" }\n```\nOptional body fields: `category_id`, `tone_guide`, `style_guide`\n\n### Setup\n\n**Step 1 \u2014 Import the LLM helper sub-workflow**\n\nThis workflow calls [Route AI prompts to Anthropic, Google Gemini, Mistral, or OpenAI](https://n8n.io/workflows/16330) as a sub-workflow for all LLM requests. Import and activate it first, then update the `CALL RESEARCHER`, `CALL WRITER` and `CALL REVIEWER` nodes to reference your imported copy.\n\n**Step 2 \u2014 Update the CONFIG node** with your:\n- `WORDPRESS_URL` \u2014 your WordPress site URL\n- `DEFAULT_CATEGORY_ID` \u2014 default category ID for new posts (1 = Uncategorized)\n- `AUTHOR_ID` \u2014 WordPress user ID to assign as post author\n- `SLACK_CHANNEL` \u2014 Slack channel ID for completion notifications (leave blank to disable)\n- `ENABLE_IMAGE_GENERATION` \u2014 set to `false` to skip Imagen 4 image generation\n- `ENABLE_INTERNAL_LINKS` \u2014 set to `false` to skip fetching recent posts for internal linking\n- `IMAGE_STYLE_PROMPT` \u2014 style prefix prepended to every Imagen 4 prompt\n- `TONE_GUIDE` \u2014 writing voice and persona for the agents (overridable per request)\n- `STYLE_GUIDE` \u2014 article structure and formatting rules (overridable per request)\n- `WEBHOOK_SECRET` \u2014 optional Bearer token to secure the endpoint (leave blank to disable)\n\n**Agent model configuration** \u2014 set provider and model for each stage independently:\n- `AGENT_RESEARCHER_PROVIDER` / `AGENT_RESEARCHER_MODEL` \u2014 outline and research (default: `anthropic` / `claude-sonnet-4-6`)\n- `AGENT_WRITER_PROVIDER` / `AGENT_WRITER_MODEL` \u2014 full draft (default: `google` / `gemini-3.5-flash`)\n- `AGENT_REVIEWER_PROVIDER` / `AGENT_REVIEWER_MODEL` \u2014 editorial review (default: `anthropic` / `claude-sonnet-4-6`)\n\nTo use a single model for all three stages, set all six fields to the same provider and model. Supported providers: `anthropic`, `google`, `mistral`, `openai`.\n\n**Step 3 \u2014 Link credentials** in the nodes listed:\n- Google AI (PaLM) API \u2192 `GENERATE IMAGE`\n- WordPress API \u2192 `FETCH RECENT POSTS`, `UPLOAD IMAGE TO WORDPRESS`, `SET IMAGE ALT TEXT`, `UPSERT TAG`, `CREATE WORDPRESS POST`, `SET RANK MATH SEO`\n- Slack OAuth2 \u2192 `SLACK NOTIFICATION`, `SLACK PIPELINE ERROR`\n\n*(LLM credentials are configured in the sub-workflow, not here.)*\n\n### Customization\n\n**(Optional) Rank Math SEO:** Requires the Rank Math plugin plus a custom mu-plugin to expose `rank_math_*` meta fields to the REST API. Place `rank-math-rest.php` in `/wp-content/mu-plugins/`. Full plugin code is in the Rank Math SEO sticky note on the canvas."
},
"typeVersion": 1
},
{
"id": "bdf2e0da-cc3b-421c-bb34-09ca86006bcf",
"name": "Sticky Note 98f2054a",
"type": "n8n-nodes-base.stickyNote",
"position": [
1776,
464
],
"parameters": {
"color": 5,
"width": 624,
"height": 256,
"content": "### Image Generation (optional)\n\nSet `ENABLE_IMAGE_GENERATION` to `false` in CONFIG to skip.\n\nWhen enabled, Google Imagen 4 generates a 16:9 featured image.\nThe filename and alt text are derived from the focus keyword.\n\nRequires a Google AI (PaLM) API credential with Imagen 4 access."
},
"typeVersion": 1
},
{
"id": "f86e564d-0d11-468f-8989-3db74c131658",
"name": "Sticky Note 70f09bc0",
"type": "n8n-nodes-base.stickyNote",
"position": [
4656,
-16
],
"parameters": {
"color": 5,
"width": 800,
"height": 736,
"content": "### Rank Math SEO (optional)\n\nSets focus keyword, SEO title, and meta description via the WordPress REST API.\n\nRequires the Rank Math SEO plugin plus a custom mu-plugin (so that WordPress loads it automatically) to expose the `rank_math_*` meta fields to the REST API.\n\n**This step is only for qualified WordPress administrators**\n\nCreate a file called `rank-math-rest.php` in `/wp-content/mu-plugins/`, containing the code below.\n\n```\n<?php\n// Register Rank Math SEO meta fields for REST API access.\nadd_action( 'rest_api_init', function () {\n $fields = [\n 'rank_math_focus_keyword',\n 'rank_math_title',\n 'rank_math_description',\n ];\n foreach ( $fields as $field ) {\n register_post_meta( 'post', $field, [\n 'show_in_rest' => true,\n 'single' => true,\n 'type' => 'string',\n 'sanitize_callback' => 'sanitize_text_field',\n 'auth_callback' => function () {\n return current_user_can( 'edit_posts' );\n },\n ] );\n }\n} );\n```"
},
"typeVersion": 1
},
{
"id": "283a9a0e-15ac-4ced-8228-6816d6bfa193",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-160,
992
],
"parameters": {
"color": 7,
"width": 1104,
"height": 624,
"content": "## 1) Initialise"
},
"typeVersion": 1
},
{
"id": "ff223f93-31eb-47ce-baa9-d9868653933d",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
960,
992
],
"parameters": {
"color": 7,
"width": 2784,
"height": 624,
"content": "## 2) Create article \u2014 Press Room"
},
"typeVersion": 1
},
{
"id": "7567c926-fb39-4add-8319-2d32602dc750",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1776,
736
],
"parameters": {
"color": 7,
"width": 1968,
"height": 304,
"content": "## 3) Create featured image"
},
"typeVersion": 1
},
{
"id": "555fa13e-309e-4958-966e-d5d679f714da",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
3760,
736
],
"parameters": {
"color": 7,
"width": 880,
"height": 496,
"content": "## 4) Create draft Wordpress post"
},
"typeVersion": 1
},
{
"id": "f3e5080a-a0e4-4647-8d14-9c43c120a96a",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
4656,
736
],
"parameters": {
"color": 7,
"width": 512,
"height": 880,
"content": "## 5) Optimise SEO"
},
"typeVersion": 1
},
{
"id": "84d29463-4ad1-4c63-b4b2-4467bf6d90aa",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
5184,
736
],
"parameters": {
"color": 7,
"width": 1024,
"height": 880,
"content": "## 6) Notify on completion"
},
"typeVersion": 1
},
{
"id": "bbaf29e3-cb0f-4c96-a9db-2ebae71226d9",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
3760,
1248
],
"parameters": {
"color": 7,
"width": 880,
"height": 368,
"content": "## Notify on failure"
},
"typeVersion": 1
},
{
"id": "3b8c303c-96d7-434e-aa22-8640b977dc31",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-112,
1296
],
"parameters": {
"path": "article-publisher",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2.1
},
{
"id": "c2f071ff-1e29-4132-bffc-f2c5349c11d7",
"name": "CONFIG",
"type": "n8n-nodes-base.set",
"position": [
112,
1296
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "wp_url",
"name": "WORDPRESS_URL",
"type": "string",
"value": "https://your-wordpress-site.com"
},
{
"id": "cat_id",
"name": "DEFAULT_CATEGORY_ID",
"type": "string",
"value": "1"
},
{
"id": "slack_ch",
"name": "SLACK_CHANNEL",
"type": "string",
"value": ""
},
{
"id": "author",
"name": "AUTHOR_ID",
"type": "string",
"value": "1"
},
{
"id": "img_en",
"name": "ENABLE_IMAGE_GENERATION",
"type": "string",
"value": "true"
},
{
"id": "int_links",
"name": "ENABLE_INTERNAL_LINKS",
"type": "string",
"value": "true"
},
{
"id": "img_style",
"name": "IMAGE_STYLE_PROMPT",
"type": "string",
"value": "Replace with your preferred image style. This prefix is prepended to every Imagen 4 prompt. Example: Retro tech lab illustration, flat design, clean lines, no text, landscape orientation, subject: "
},
{
"id": "tone",
"name": "TONE_GUIDE",
"type": "string",
"value": "Write in first person as the site author.\n\nReplace this guide with a description of your own writing voice, audience, and goals. Example structure:\n\nVoice: [describe your preferred tone \u2014 e.g. direct and warm, technical but accessible, never salesy]\n\nFrame content as: [e.g. here is what I tried, here is what I found, here is how you might apply it]\n\nReader: [describe your target audience \u2014 their technical level, what they are trying to do, what they already know]\n\n[Your site name] is [brief description \u2014 what it is, what it is for, what tone reflects it best]."
},
{
"id": "style",
"name": "STYLE_GUIDE",
"type": "string",
"value": "Structure every article as follows:\n\n- Opening paragraph: the first sentence should carry weight on its own. \n\n- H2 sections for main topics, H3 for subsections where needed. \n\n- Use bullet lists sparingly \u2014 only for genuine lists of 3+ parallel items. Never use bullets to avoid writing proper sentences.\n\n- Tables for comparisons, not bullets.\n\n- Closing paragraph: one concrete takeaway or honest assessment. End on something useful or thought-provoking.\n\n- Figures and estimates: for any specific figure \u2014 a percentage, price, benchmark, timing, or statistic \u2014 either (a) name the source inline, or (b) clearly label it as a personal observation or rough estimate. Do not state estimates as established facts.\n\n- Do not include specific pricing figures for third-party services. Reference the service's pricing page instead.\n\n- Ideal length: 800\u20131400 words for most articles. Go longer only if the topic genuinely requires it."
},
{
"id": "webhook_sec",
"name": "WEBHOOK_SECRET",
"type": "string",
"value": ""
},
{
"id": "res_prov",
"name": "AGENT_RESEARCHER_PROVIDER",
"type": "string",
"value": "anthropic"
},
{
"id": "res_model",
"name": "AGENT_RESEARCHER_MODEL",
"type": "string",
"value": "claude-sonnet-4-6"
},
{
"id": "wri_prov",
"name": "AGENT_WRITER_PROVIDER",
"type": "string",
"value": "google"
},
{
"id": "wri_model",
"name": "AGENT_WRITER_MODEL",
"type": "string",
"value": "gemini-3.5-flash"
},
{
"id": "rev_prov",
"name": "AGENT_REVIEWER_PROVIDER",
"type": "string",
"value": "anthropic"
},
{
"id": "rev_model",
"name": "AGENT_REVIEWER_MODEL",
"type": "string",
"value": "claude-sonnet-4-6"
},
{
"id": "imagen",
"name": "IMAGEN_MODEL",
"type": "string",
"value": "imagen-4.0-fast-generate-001"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "498cbb5f-f46d-4fc5-b04c-c5320c28f696",
"name": "VALIDATE AUTH",
"type": "n8n-nodes-base.if",
"position": [
336,
1296
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "1",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('CONFIG').item.json.WEBHOOK_SECRET }}",
"rightValue": ""
},
{
"id": "2",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Webhook').item.json.headers.authorization ?? '' }}",
"rightValue": "={{ 'Bearer ' + $('CONFIG').item.json.WEBHOOK_SECRET }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "7d9f35db-9461-4008-a2ad-ac740c64dc68",
"name": "PREPARE INPUT",
"type": "n8n-nodes-base.set",
"position": [
560,
1200
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "1",
"name": "prompt",
"type": "string",
"value": "={{ $('Webhook').item.json.body.prompt ?? $('Webhook').item.json.body.topic ?? '' }}"
},
{
"id": "2",
"name": "title_override",
"type": "string",
"value": "={{ $('Webhook').item.json.body.title ?? '' }}"
},
{
"id": "3",
"name": "content_override",
"type": "string",
"value": "={{ $('Webhook').item.json.body.content ?? '' }}"
},
{
"id": "4",
"name": "category_id",
"type": "string",
"value": "={{ $('Webhook').item.json.body.category_id ?? $('CONFIG').item.json.DEFAULT_CATEGORY_ID }}"
},
{
"id": "5",
"name": "tone_guide",
"type": "string",
"value": "={{ $('Webhook').item.json.body.tone_guide ?? $('CONFIG').item.json.TONE_GUIDE }}"
},
{
"id": "6",
"name": "style_guide",
"type": "string",
"value": "={{ $('Webhook').item.json.body.style_guide ?? $('CONFIG').item.json.STYLE_GUIDE }}"
},
{
"id": "7",
"name": "enable_image_generation",
"type": "string",
"value": "={{ $('Webhook').item.json.body.enable_image_generation !== undefined ? String($('Webhook').item.json.body.enable_image_generation) : '' }}"
},
{
"id": "8",
"name": "enable_internal_links",
"type": "string",
"value": "={{ $('Webhook').item.json.body.enable_internal_links !== undefined ? String($('Webhook').item.json.body.enable_internal_links) : '' }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "898de764-f825-4518-a237-96ab86255253",
"name": "REJECT REQUEST",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
560,
1408
],
"parameters": {
"options": {
"responseCode": 401
},
"respondWith": "json",
"responseBody": "{\"error\":\"Unauthorized\"}"
},
"typeVersion": 1.5
},
{
"id": "171d5067-6d3a-4117-9db5-7693adcd57f3",
"name": "BUILD OUTLINE PROMPT",
"type": "n8n-nodes-base.code",
"position": [
1040,
1184
],
"parameters": {
"jsCode": "const cfg = $('CONFIG').item.json;\nconst inp = $('PREPARE INPUT').item.json;\n\nconst provider = (cfg.AGENT_RESEARCHER_PROVIDER || 'anthropic').toLowerCase();\nconst model = cfg.AGENT_RESEARCHER_MODEL || 'claude-sonnet-4-6';\nconst modelKey = provider === 'google' ? 'google_model' : provider === 'mistral' ? 'mistral_model' : 'anthropic_model';\n\nreturn [{\n json: {\n llm_provider: provider,\n [modelKey]: model,\n response_format: 'json',\n max_tokens: 4000,\n temperature: 0.4,\n systemPrompt: 'You are a research editor and content strategist. Analyse the brief and produce a structured article outline a writer can follow precisely. Return ONLY a valid JSON object, no markdown, no code fences.',\n userPrompt: 'Brief: ' + inp.prompt + '\\n\\nTone guide:\\n' + inp.tone_guide + '\\n\\nStyle guide:\\n' + inp.style_guide + '\\n\\nReturn a JSON object with these exact fields:\\n- title (string)\\n- focus_keyword (string)\\n- angle (one-sentence article thesis)\\n- h2_sections (array of {heading: string, key_points: string[], notes: string})\\n- image_subject (one sentence visual scene for featured image)\\n- affiliate_flag: if the article touches products or services that have affiliate programmes, write a short plain-English description of the opportunities (e.g. \"Copyscape pay-per-search affiliate \u2014 link to plans page\"). If none, use the string \"none\".\\n- tags (string[])\\n\\nReturn ONLY valid JSON, no markdown.'\n }\n}];"
},
"typeVersion": 2
},
{
"id": "1f8e303c-333a-46bf-993f-fea5301ae21d",
"name": "CALL RESEARCHER",
"type": "n8n-nodes-base.executeWorkflow",
"onError": "continueErrorOutput",
"position": [
1280,
1184
],
"parameters": {
"options": {
"waitForSubWorkflow": true
},
"workflowId": {
"__rl": true,
"mode": "list",
"value": "h0ZOAW0PzyTkjUvN",
"cachedResultName": "Route AI prompts to Anthropic, Google Gemini, Mistral, or OpenAI"
},
"workflowInputs": {
"value": {},
"schema": [],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
}
},
"typeVersion": 1.3
},
{
"id": "a9f45309-0353-4268-a6d5-c0b5e46e8c32",
"name": "PARSE OUTLINE",
"type": "n8n-nodes-base.code",
"position": [
1584,
1120
],
"parameters": {
"jsCode": "const rawText = $input.item.json.llm_response;\nif (!rawText) throw new Error('Researcher returned empty response');\n\nconst cleaned = rawText\n .replace(/^```json\\s*/i, '')\n .replace(/^```\\s*/i, '')\n .replace(/```\\s*$/i, '')\n .trim();\n\ntry {\n const outline = JSON.parse(cleaned);\n const inp = $('PREPARE INPUT').item.json;\n return [{\n json: {\n outline,\n prompt: inp.prompt,\n tone_guide: inp.tone_guide,\n style_guide: inp.style_guide,\n category_id: inp.category_id,\n }\n }];\n} catch (e) {\n throw new Error('Researcher outline could not be parsed as JSON. Raw: ' + rawText.substring(0, 300));\n}"
},
"typeVersion": 2
},
{
"id": "db2e12f9-00ce-45a5-983d-a3a2d630d071",
"name": "CHECK IMAGE ENABLED",
"type": "n8n-nodes-base.if",
"position": [
1872,
896
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('PREPARE INPUT').item.json.enable_image_generation !== '' ? $('PREPARE INPUT').item.json.enable_image_generation : $('CONFIG').item.json.ENABLE_IMAGE_GENERATION }}",
"rightValue": "true"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "11d4e598-0c09-446f-9d36-b4edc8023e9a",
"name": "GENERATE IMAGE",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"position": [
2096,
880
],
"parameters": {
"url": "=https://generativelanguage.googleapis.com/v1beta/models/{{ $('CONFIG').item.json.IMAGEN_MODEL }}:predict",
"method": "POST",
"options": {},
"jsonBody": "={{ { \"instances\": [{ \"prompt\": $(\"CONFIG\").item.json.IMAGE_STYLE_PROMPT + $(\"PARSE OUTLINE\").item.json.outline.image_subject + \". No text. No words. No letters.\" }], \"parameters\": { \"sampleCount\": 1, \"aspectRatio\": \"16:9\" } } }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googlePalmApi"
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "cbd49a30-48bf-44d3-a698-dd126ee4c534",
"name": "DECODE IMAGE",
"type": "n8n-nodes-base.code",
"onError": "continueErrorOutput",
"position": [
2320,
832
],
"parameters": {
"jsCode": "const prediction = $input.item.json.predictions[0];\nconst base64 = prediction.bytesBase64Encoded;\nconst mimeType = prediction.mimeType || 'image/png';\nconst focusKeyword = $('PARSE OUTLINE').item.json.outline.focus_keyword || 'featured';\nconst slug = focusKeyword.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/, '');\nconst filename = slug + '.png';\nconst binaryData = await this.helpers.prepareBinaryData(\n Buffer.from(base64, 'base64'),\n filename,\n mimeType\n);\nreturn [{ json: { image_filename: filename }, binary: { data: binaryData } }];"
},
"typeVersion": 2
},
{
"id": "22c43753-d6bb-4716-8dbe-3afb7a89fb82",
"name": "UPLOAD IMAGE TO WORDPRESS",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"position": [
2560,
784
],
"parameters": {
"url": "={{ $('CONFIG').item.json.WORDPRESS_URL }}/wp-json/wp/v2/media",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "binaryData",
"sendHeaders": true,
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Disposition",
"value": "=attachment; filename=\"{{ $('DECODE IMAGE').item.json.image_filename }}\""
}
]
},
"inputDataFieldName": "data",
"nodeCredentialType": "wordpressApi"
},
"typeVersion": 4.4
},
{
"id": "bc510b2b-2c6e-4a12-99eb-9aa3b2c1e4c4",
"name": "SET IMAGE ALT TEXT",
"type": "n8n-nodes-base.httpRequest",
"position": [
2784,
768
],
"parameters": {
"url": "={{ $('CONFIG').item.json.WORDPRESS_URL }}/wp-json/wp/v2/media/{{ $json.id }}",
"method": "POST",
"options": {},
"jsonBody": "={{ { \"alt_text\": $('PARSE OUTLINE').item.json.outline.focus_keyword } }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "wordpressApi"
},
"typeVersion": 4.4
},
{
"id": "c9f5402b-8908-4564-85c9-beb5d2d4d023",
"name": "SET MEDIA ID",
"type": "n8n-nodes-base.set",
"position": [
2992,
768
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "1",
"name": "media_id",
"type": "number",
"value": "={{ ($input.item.json.id && $input.item.json.id > 0) ? $input.item.json.id : 0 }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a7f35206-4cbd-41fb-ab32-d908636945ca",
"name": "MERGE IMAGE RESULT",
"type": "n8n-nodes-base.merge",
"position": [
3216,
896
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "d3f882b4-0fde-4d2e-8724-7d22a9fb5a2a",
"name": "SET NO IMAGE",
"type": "n8n-nodes-base.set",
"position": [
2768,
1072
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "1",
"name": "media_id",
"type": "number",
"value": 0
}
]
}
},
"typeVersion": 3.4
},
{
"id": "960b7ab8-1205-4922-b8dc-e4adcc7c3107",
"name": "FETCH RECENT POSTS",
"type": "n8n-nodes-base.httpRequest",
"position": [
1872,
1376
],
"parameters": {
"url": "={{ $('CONFIG').item.json.WORDPRESS_URL }}/wp-json/wp/v2/posts?per_page=100&status=publish&_fields=id,title,link",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "wordpressApi"
},
"typeVersion": 4.4
},
{
"id": "34149402-849b-4306-acdb-cc7b1f3f678e",
"name": "FILTER INTERNAL LINKS",
"type": "n8n-nodes-base.code",
"position": [
2096,
1376
],
"parameters": {
"jsCode": "// Collect posts \u2014 HTTP Request v4 splits array responses into separate items\nconst allItems = $input.all();\nconst posts = allItems.map(function(item) { return item.json; }).filter(function(p) { return p && p.link; });\n\nconst cfg = $('CONFIG').item.json;\nconst inp = $('PREPARE INPUT').item.json;\nconst outlineData = $('PARSE OUTLINE').item.json;\nconst outline = outlineData.outline;\n\nconst flagRaw = (inp.enable_internal_links !== undefined && inp.enable_internal_links !== '')\n ? String(inp.enable_internal_links).toLowerCase()\n : String(cfg.ENABLE_INTERNAL_LINKS || 'true').toLowerCase();\nconst enabled = flagRaw === 'true';\n\nlet internalLinks = [];\n\nif (enabled && posts.length > 0) {\n const stopwords = new Set(['a','an','the','and','or','in','on','at','to','for','of','with','how','what','why','is','are','was','were','be','been','being','have','has','had','do','does','did','will','would','could','should','may','might','that','this','these','those','it','its','from','by','as','but','not','your','my','our','their','we','you','i','he','she','they','about','which','can','into','more','some','than','then','there','when','where','who','all','also','just','like','over','after','before','using','use','used']);\n\n function tokenise(str) {\n return String(str || '').toLowerCase().replace(/[^a-z0-9 ]/g, ' ').split(/\\s+/).filter(function(w) { return w.length > 2 && !stopwords.has(w); });\n }\n\n const signals = [outline.focus_keyword || '', outline.title || ''].concat(outline.tags || []);\n const signalTokens = new Set(signals.reduce(function(acc, s) { return acc.concat(tokenise(s)); }, []));\n\n const scored = posts.map(function(post) {\n const titleText = post.title && typeof post.title === 'object' ? (post.title.rendered || '') : (post.title || '');\n const cleanTitle = titleText.replace(/<[^>]+>/g, '');\n const score = tokenise(cleanTitle).filter(function(t) { return signalTokens.has(t); }).length;\n return { title: cleanTitle, url: post.link || '', score: score };\n }).filter(function(p) { return p.score > 0; })\n .sort(function(a, b) { return b.score - a.score; })\n .slice(0, 5);\n\n internalLinks = scored.map(function(p) { return { title: p.title, url: p.url }; });\n}\n\nreturn [{ json: Object.assign({}, outlineData, { internal_links: internalLinks }) }];"
},
"typeVersion": 2
},
{
"id": "d3287760-bff0-4c00-abff-0bc5569a7b3f",
"name": "BUILD DRAFT PROMPT",
"type": "n8n-nodes-base.code",
"position": [
2320,
1376
],
"parameters": {
"jsCode": "const cfg = $('CONFIG').item.json;\nconst outline = $input.item.json.outline;\nconst toneGuide = $input.item.json.tone_guide;\nconst styleGuide = $input.item.json.style_guide;\nconst internalLinks = $input.item.json.internal_links || [];\n\nconst provider = (cfg.AGENT_WRITER_PROVIDER || 'google').toLowerCase();\nconst model = cfg.AGENT_WRITER_MODEL || 'gemini-3.5-flash';\nconst modelKey = provider === 'google' ? 'google_model' : provider === 'mistral' ? 'mistral_model' : 'anthropic_model';\n\nconst sections = (outline.h2_sections || []).map(function(s) {\n return '## ' + s.heading + '\\nKey points: ' + (s.key_points || []).join('; ') + '\\nNotes: ' + (s.notes || 'none');\n}).join('\\n\\n');\n\nconst internalLinksBlock = internalLinks.length > 0\n ? '\\n\\nINTERNAL LINKS \u2014 where contextually appropriate, naturally weave links to these existing posts into the article body using HTML anchor tags. Only link where the context genuinely invites it; do not force them or mention them out of place:\\n' + internalLinks.map(function(l) { return '- ' + l.title + ': ' + l.url; }).join('\\n')\n : '';\n\nreturn [{\n json: {\n llm_provider: provider,\n [modelKey]: model,\n response_format: 'text',\n max_tokens: 12000,\n temperature: 0.7,\n systemPrompt: 'You are a skilled content writer. Write in the exact voice described in the tone guide. Follow the style guide for structure and formatting. Write the article body directly in HTML \u2014 do not wrap output in JSON or code fences.',\n userPrompt: 'Write a full article draft based on the outline below.\\n\\nTONE GUIDE:\\n' + toneGuide + '\\n\\nSTYLE GUIDE:\\n' + styleGuide + '\\n\\nOUTLINE:\\nTitle: ' + outline.title + '\\nAngle: ' + outline.angle + '\\nFocus keyword: ' + outline.focus_keyword + '\\n\\nSections:\\n' + sections + internalLinksBlock + '\\n\\nWrite the full HTML article body using <h2>, <p>, <ul>/<li> tags. After the article body, add a line reading exactly \"EXCERPT:\" followed by a two-sentence summary paragraph.\\n\\nWrite only article content \u2014 no JSON, no code fences, no preamble.'\n }\n}];"
},
"typeVersion": 2
},
{
"id": "ea6824d6-00f6-433e-a37c-808283dd1b4c",
"name": "CALL WRITER",
"type": "n8n-nodes-base.executeWorkflow",
"onError": "continueErrorOutput",
"position": [
2544,
1376
],
"parameters": {
"options": {
"waitForSubWorkflow": true
},
"workflowId": {
"__rl": true,
"mode": "list",
"value": "h0ZOAW0PzyTkjUvN",
"cachedResultName": "Route AI prompts to Anthropic, Google Gemini, Mistral, or OpenAI"
},
"workflowInputs": {
"value": {},
"schema": [],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
}
},
"typeVersion": 1.3
},
{
"id": "73fa115f-421c-4957-b620-a6c0fddb322e",
"name": "BUILD REVIEW PROMPT",
"type": "n8n-nodes-base.code",
"position": [
2768,
1312
],
"parameters": {
"jsCode": "const cfg = $('CONFIG').item.json;\nconst draftRaw = $input.item.json.llm_response || '';\n\nconst draftStripped = draftRaw\n .replace(/^```[a-z]*\\s*/i, '')\n .replace(/```\\s*$/i, '')\n .trim();\n\nconst excerptMatch = draftStripped.match(/EXCERPT:\\s*([\\s\\S]+?)(?:\\n\\n[\\s\\S]*|$)/i);\nconst excerpt = excerptMatch ? excerptMatch[1].trim() : '';\nconst draftContent = draftStripped.replace(/\\n*EXCERPT:[\\s\\S]*/i, '').trim();\n\nconst outline = $('PARSE OUTLINE').item.json.outline;\nconst originalPrompt = $('PREPARE INPUT').item.json.prompt;\n\nconst provider = (cfg.AGENT_REVIEWER_PROVIDER || 'anthropic').toLowerCase();\nconst model = cfg.AGENT_REVIEWER_MODEL || 'claude-sonnet-4-6';\nconst modelKey = provider === 'google' ? 'google_model' : provider === 'mistral' ? 'mistral_model' : 'anthropic_model';\n\nreturn [{\n json: {\n llm_provider: provider,\n [modelKey]: model,\n response_format: 'text',\n max_tokens: 12000,\n temperature: 0.3,\n _draft_content: draftContent,\n _draft_excerpt: excerpt,\n _outline: outline,\n systemPrompt: 'IMPORTANT: Begin your response immediately with the opening HTML tag of the revised article (<p> or <h2>). Do not output any analysis, reasoning, checklists, voice checks, notes, or commentary before the HTML content. Your internal review process must not appear in the output.\\n\\nYou are a technical editor and voice guardian. Your job is to ensure every post reflects the author\\'s voice consistently \u2014 not a content agency tone, not a corporate blog, and not generic AI output. Review the draft against the brief and the voice criteria in the tone guide, then rewrite any sections that fail. Output TWO sections separated by the exact line ---JSON---\\n\\nSection 1: the revised full HTML article body (no wrapping tags, no preamble, no commentary \u2014 start directly with <p> or <h2>).\\n\\nSection 2: a JSON object on a single line with these exact fields:\\n- title (string)\\n- excerpt (two sentences, string)\\n- tags (string[])\\n- seo_title (string, MUST be under 60 characters AND must contain the focus_keyword verbatim \u2014 write this as a keyword-forward search snippet, not a copy of the post title)\\n- seo_description (string, MUST be under 155 characters AND must contain the focus_keyword verbatim)\\n- focus_keyword (string \u2014 a short 2\u20134 word phrase that naturally describes the topic)\\n- image_subject (string, one-sentence visual scene)\\n- affiliate_flag (string \u2014 MUST be a plain-English description of affiliate opportunities in this article, or the string \"none\" if there are none. NEVER output a boolean true or false.)\\n\\nVOICE CHECKLIST \u2014 apply these silently while editing. Do not output the checklist or any evaluation notes:\\n\\n1. OPENING \u2014 Read the first sentence. If it functions only as personal backstory or throat-clearing rather than starting at the problem or observation, rewrite it. The first sentence should carry weight on its own.\\n\\n2. No corporate filler phrases (\"highly capable\", \"seamlessly integrates\", \"robust solution\", \"leverage\").\\n\\n3. Apply the language conventions and voice described in the tone guide throughout.\\n\\n4. No fabricated statistics \u2014 either name the source inline or label it as a personal estimate.\\n\\n5. No third-party pricing figures \u2014 reference the service\\'s pricing page instead.\\n\\n6. Closing paragraph: one concrete takeaway or honest assessment. No \"In conclusion\" or summary restatement.',\n userPrompt: 'Original brief: ' + originalPrompt + '\\n\\nOutline title: ' + (outline.title || '') + '\\nFocus keyword: ' + (outline.focus_keyword || '') + '\\n\\nTone guide:\\n' + cfg.TONE_GUIDE + '\\n\\nDRAFT:\\n' + draftContent + '\\n\\n' + (excerpt ? 'EXCERPT FROM DRAFT:\\n' + excerpt : '') + '\\n\\nReview and revise. Begin your response immediately with <p> or <h2> \u2014 no preamble, no notes, no checklist output. Output the revised HTML body, then ---JSON--- on its own line, then the metadata JSON object.'\n }\n}];"
},
"typeVersion": 2
},
{
"id": "05b18de2-e2e6-4f2f-b2e3-7c33ff50e440",
"name": "CALL REVIEWER",
"type": "n8n-nodes-base.executeWorkflow",
"onError": "continueErrorOutput",
"position": [
3008,
1312
],
"parameters": {
"options": {
"waitForSubWorkflow": true
},
"workflowId": {
"__rl": true,
"mode": "list",
"value": "h0ZOAW0PzyTkjUvN",
"cachedResultName": "Route AI prompts to Anthropic, Google Gemini, Mistral, or OpenAI"
},
"workflowInputs": {
"value": {},
"schema": [],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
}
},
"typeVersion": 1.3
},
{
"id": "6ab7b35c-313b-4dff-8037-c73cd0f5b3b3",
"name": "PARSE ARTICLE",
"type": "n8n-nodes-base.code",
"position": [
3216,
1296
],
"parameters": {
"jsCode": "const rawText = $input.item.json.llm_response;\nif (!rawText) throw new Error('Reviewer returned empty response');\n\nconst cleaned = rawText\n .replace(/^```[a-z]*\\s*/i, '')\n .replace(/```\\s*$/i, '')\n .trim();\n\nconst sepIdx = cleaned.indexOf('---JSON---');\n\nlet content, meta;\n\nif (sepIdx !== -1) {\n content = cleaned.substring(0, sepIdx).trim();\n\n const firstHtmlIdx = content.search(/<(p|h[1-6]|ul|ol|pre|blockquote)[\\s>]/i);\n if (firstHtmlIdx > 0) content = content.substring(firstHtmlIdx);\n\n const jsonPart = cleaned.substring(sepIdx + '---JSON---'.length).trim();\n try {\n meta = JSON.parse(jsonPart);\n } catch (e) {\n const outlineData = $('PARSE OUTLINE').first().json.outline || {};\n const extractStr = (field) => {\n const m = jsonPart.match(new RegExp('\"' + field + '\"\\\\s*:\\\\s*\"((?:[^\\\\\"]|\\\\\\\\.)*)\"'));\n return m ? m[1] : null;\n };\n const tagsMatch = jsonPart.match(/\"tags\"\\s*:\\s*\\[([^\\]]*)\\]/);\n let tags = [];\n if (tagsMatch) {\n const rawTags = tagsMatch[1].match(/\"([^\"]*)\"/g);\n if (rawTags) tags = rawTags.map(t => t.replace(/\"/g, ''));\n }\n meta = {\n title: extractStr('title') || outlineData.title || 'Untitled',\n excerpt: extractStr('excerpt') || '',\n tags: tags.length ? tags : (outlineData.tags || []),\n seo_title: extractStr('seo_title') || outlineData.title || '',\n seo_description: extractStr('seo_description') || '',\n focus_keyword: extractStr('focus_keyword') || outlineData.focus_keyword || '',\n image_subject: extractStr('image_subject') || outlineData.image_subject || '',\n affiliate_flag: extractStr('affiliate_flag') || 'none'\n };\n }\n} else {\n try {\n const article = JSON.parse(cleaned);\n content = article.content;\n meta = article;\n } catch(e) {\n const outlineData = $('PARSE OUTLINE').first().json.outline || {};\n const draftExcerpt = $('BUILD REVIEW PROMPT').first().json._draft_excerpt || '';\n content = cleaned;\n meta = {\n title: outlineData.title || 'Untitled',\n excerpt: draftExcerpt,\n tags: outlineData.tags || [],\n seo_title: outlineData.title || '',\n seo_description: '',\n focus_keyword: outlineData.focus_keyword || '',\n image_subject: outlineData.image_subject || '',\n affiliate_flag: outlineData.affiliate_flag || 'none'\n };\n }\n}\n\nfunction sanitiseHtml(html) {\n if (!html) return '';\n return html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, '')\n .replace(/<iframe[\\s\\S]*?<\\/iframe>/gi, '')\n .replace(/<style[\\s\\S]*?<\\/style>/gi, '')\n .replace(/<object[\\s\\S]*?<\\/object>/gi, '')\n .replace(/<embed[^>]*>/gi, '')\n .replace(/<form[\\s\\S]*?<\\/form>/gi, '')\n .replace(/ on[a-z]+\\s*=\\s*\"[^\"]*\"/gi, '')\n .replace(/ on[a-z]+\\s*=\\s*'[^']*'/gi, '')\n .replace(/ on[a-z]+\\s*=\\s*[^\\s>]*/gi, '')\n .replace(/(href|src)\\s*=\\s*\"javascript:[^\"]*\"/gi, '$1=\"#\"')\n .replace(/(href|src)\\s*=\\s*'javascript:[^']*'/gi, \"$1='#'\");\n}\n\nfunction truncateAtWord(str, maxLen) {\n if (!str) return '';\n const s = String(str).trim();\n if (s.length <= maxLen) return s;\n const cut = s.substring(0, maxLen);\n const lastSpace = cut.lastIndexOf(' ');\n return (lastSpace > 0 ? cut.substring(0, lastSpace) : cut) + '\u2026';\n}\n\nfunction normaliseAffiliateFlag(val) {\n if (val === true || val === 'true') return 'Affiliate opportunities present \u2014 check outline notes for details';\n if (!val || val === false || val === 'false') return 'none';\n return String(val);\n}\n\nconst safeContent = sanitiseHtml(content);\nconst seoTitle = truncateAtWord(meta.seo_title || meta.title || '', 60);\nconst seoDesc = truncateAtWord(meta.seo_description || '', 155);\n\nreturn [{ json: {\n title: meta.title,\n content: safeContent,\n excerpt: meta.excerpt,\n tags: meta.tags,\n seo_title: seoTitle,\n seo_description: seoDesc,\n focus_keyword: meta.focus_keyword || (meta.tags && meta.tags[0]),\n image_subject: meta.image_subject,\n affiliate_flag: normaliseAffiliateFlag(meta.affiliate_flag)\n}}];"
},
"typeVersion": 2
},
{
"id": "28d2363f-bd5a-427c-baaa-10502e8b9fe4",
"name": "SYNC BRANCHES",
"type": "n8n-nodes-base.merge",
"position": [
3584,
1008
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "84a2b386-ee54-48ce-850a-e8de0306de94",
"name": "SPLIT TAGS",
"type": "n8n-nodes-base.code",
"position": [
3856,
1008
],
"parameters": {
"jsCode": "const tags = $('PARSE ARTICLE').first().json.tags || [];\nreturn tags.map(tag => ({ json: { tag_name: tag } }));"
},
"typeVersion": 2
},
{
"id": "b03ceebc-49a9-4b2e-85d5-9b6d2f4ab31a",
"name": "UPSERT TAG",
"type": "n8n-nodes-base.httpRequest",
"position": [
4064,
1008
],
"parameters": {
"url": "={{ $('CONFIG').first().json.WORDPRESS_URL }}/wp-json/wp/v2/tags",
"method": "POST",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"jsonBody": "={{ { \"name\": $json.tag_name } }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "wordpressApi"
},
"typeVersion": 4.4
},
{
"id": "6a1dd435-8c28-408b-b974-ae06bd82c96c",
"name": "COLLECT TAG IDS",
"type": "n8n-nodes-base.code",
"position": [
4272,
1008
],
"parameters": {
"jsCode": "const tagIds = $input.all().map(item => {\n const r = item.json;\n return r.id || (r.data && r.data.term_id) || null;\n}).filter(id => id !== null && id !== undefined && id !== 0);\nreturn [{ json: { tag_ids: tagIds } }];"
},
"typeVersion": 2
},
{
"id": "2aa78aa2-f829-484b-b3b3-3be1be3b4e22",
"name": "CREATE WORDPRESS POST",
"type": "n8n-nodes-base.httpRequest",
"position": [
4480,
1008
],
"parameters": {
"url": "={{ $('CONFIG').first().json.WORDPRESS_URL }}/wp-json/wp/v2/posts",
"method": "POST",
"options": {},
"jsonBody": "={{ { \"title\": $(\"PARSE ARTICLE\").first().json.title, \"content\": $(\"PARSE ARTICLE\").first().json.content, \"excerpt\": $(\"PARSE ARTICLE\").first().json.excerpt, \"status\": \"draft\", \"slug\": $(\"PARSE ARTICLE\").first().json.focus_keyword.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''), \"categories\": [Number($(\"PREPARE INPUT\").first().json.category_id)], \"tags\": $(\"COLLECT TAG IDS\").first().json.tag_ids, \"featured_media\": $(\"MERGE IMAGE RESULT\").first().json.media_id, \"author\": Number($(\"CONFIG\").first().json.AUTHOR_ID) } }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "wordpressApi"
},
"typeVersion": 4.4
},
{
"id": "172d82b2-7b08-4aae-8935-9f38ecf6f57a",
"name": "SET RANK MATH SEO",
"type": "n8n-nodes-base.httpRequest",
"position": [
4864,
1008
],
"parameters": {
"url": "={{ $('CONFIG').first().json.WORDPRESS_URL }}/wp-json/wp/v2/posts/{{ $('CREATE WORDPRESS POST').first().json.id }}",
"method": "POST",
"options": {},
"jsonBody": "={{ { \"meta\": { \"rank_math_focus_keyword\": $(\"PARSE ARTICLE\").first().json.focus_keyword, \"rank_math_title\": $(\"PARSE ARTICLE\").first().json.seo_title, \"rank_math_description\": $(\"PARSE ARTICLE\").first().json.seo_description } } }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "wordpressApi"
},
"typeVersion": 4.4
},
{
"id": "c68e01aa-2bad-4805-8b21-5fc952df0f69",
"name": "CHECK SLACK CHANNEL",
"type": "n8n-nodes-base.if",
"position": [
5280,
1008
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1",
"operator": {
"type": "string",
"operation": "notEmpty"
},
"leftValue": "={{ $('CONFIG').first().json.SLACK_CHANNEL }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "4dcba62b-ae68-4ff6-b6eb-b778f70e2a17",
"name": "COLLECT LLM COSTS",
"type": "n8n-nodes-base.code",
"position": [
5504,
944
],
"parameters": {
"jsCode": "const calls = [\n { label: 'researcher', data: $('CALL RESEARCHER').first().json },\n { label: 'writer', data: $('CALL WRITER').first().json },\n { label: 'reviewer', data: $('CALL REVIEWER').first().json },\n];\n\nlet totalInput = 0;\nlet totalOutput = 0;\nlet totalCost = 0;\nlet hasCost = false;\nconst breakdown = [];\n\nfor (const { label, data } of calls) {\n const inp = data._input_tokens ?? 0;\n const out = data._output_tokens ?? 0;\n const cost = data._estimated_cost_usd ?? null;\n totalInput += inp;\n totalOutput += out;\n if (cost !== null) {\n totalCost += cost;\n hasCost = true;\n }\n breakdown.push({\n agent: label,\n model: data.model_used || '?',\n provider: data.provider_used || '?',\n input_tokens: inp,\n output_tokens: out,\n estimated_cost_usd: cost,\n });\n}\n\nreturn [{\n json: {\n ...$input.first().json,\n _llm_total_input_tokens: totalInput,\n _llm_total_output_tokens: totalOutput,\n _llm_total_cost_usd: hasCost ? totalCost : null,\n _llm_breakdown: breakdown,\n }\n}];"
},
"typeVersion": 2
},
{
"id": "ce64d4f5-69b6-4490-95e8-9d4a0fc22ed8",
"name": "SLACK NOTIFICATION",
"type": "n8n-nodes-base.slack",
"position": [
5744,
944
],
"parameters": {
"text": "=\ud83d\udcdd *New draft ready for review*\n*Title:* {{ $(\"PARSE ARTICLE\").first().json.title }}\n*Edit:* {{ $(\"CONFIG\").first().json.WORDPRESS_URL }}/wp-admin/post.php?post={{ $(\"CREATE WORDPRESS POST\").first().json.id }}&action=edit\n*Focus keyword:* {{ $('PARSE ARTICLE').first().json.focus_keyword }}\n*Tags:* {{ $('PARSE ARTICLE').first().json.tags.join(', ') }}\n*Affiliate:* {{ ($('PARSE ARTICLE').first().json.affiliate_flag && $('PARSE ARTICLE').first().json.affiliate_flag !== 'false' && $('PARSE ARTICLE').first().json.affiliate_flag !== 'none') ? '\ud83d\udcb0 ' + $('PARSE ARTICLE').first().json.affiliate_flag : '\u2014' }}\n*Image:* {{ $('MERGE IMAGE RESULT').first().json.media_id > 0 ? '\u2705 generated (id: ' + $('MERGE IMAGE RESULT').first().json.media_id + ')' : '\u26a0\ufe0f none \u2014 skipped or failed' }}\n*LLM usage (est.):* {{ $json._llm_total_input_tokens.toLocaleString() }} in / {{ $json._llm_total_output_tokens.toLocaleString() }} out tokens{{ $json._llm_total_cost_usd !== null ? ' \u00b7 ~$' + $json._llm_total_cost_usd.toFixed(4) : '' }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "={{ $('CONFIG').first().json.SLACK_CHANNEL }}"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2.5
},
{
"id": "b3c6e075-a988-41b8-99ff-c4a78766193e",
"name": "RESPOND TO WEBHOOK",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
6016,
1024
],
"parameters": {
"options": {
"responseCode": 200
},
"respondWith": "json",
"responseBody": "={{ { \"success\": true, \"post_id\": $(\"CREATE WORDPRESS POST\").first().json.id, \"draft_url\": $(\"CONFIG\").first().json.WORDPRESS_URL + \"/?p=\" + $(\"CREATE WORDPRESS POST\").first().json.id } }}"
},
"typeVersion": 1.5
},
{
"id": "6e526a05-d7e8-4f9b-906b-e8a8a77b5514",
"name": "HANDLE PIPELINE FAILURE",
"type": "n8n-nodes-base.code",
"position": [
3872,
1424
],
"parameters": {
"jsCode": "const stageMap = {\n 'CALL RESEARCHER': 'Outline Researcher',\n 'CALL WRITER': 'Draft Writer',\n 'CALL REVIEWER': 'Article Reviewer'\n};\n\nconst stage = stageMap[$prevNode.name] || $prevNode.name;\nconst errorMsg = typeof $json.error === 'object'\n ? ($json.error.message || JSON.stringify($json.error))\n : String($json.error || 'Unknown error');\n\nconst channel = $('CONFIG').item.json.SLACK_CHANNEL;\n\nreturn [{\n json: {\n stage,\n error: errorMsg,\n channel,\n slack_text: '\u26a0\ufe0f *Pipeline failure* \u2014 stage: *' + stage + '*\\nError: ' + errorMsg + '\\n_Workflow: trigger.wordpress_article_publisher_'\n }\n}];"
},
"typeVersion": 2
},
{
"id": "0bd33137-57aa-4e07-8c8b-f3d95fc46f42",
"name": "SLACK PIPELINE ERROR",
"type": "n8n-nodes-base.slack",
"position": [
4112,
1424
],
"parameters": {
"text": "={{ $json.slack_text }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.channel }}"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2.5
},
{
"id": "9ee9b568-7485-4130-9523-00effd65da68",
"name": "RESPOND WITH 500",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
4352,
1424
],
"parameters": {
"options": {
"responseCode": 500
},
"respondWith": "json",
"responseBody": "={{ { \"error\": \"Pipeline failed at stage: \" + $('HANDLE PIPELINE FAILURE').item.json.stage, \"detail\": $('HANDLE PIPELINE FAILURE').item.json.error } }}"
},
"typeVersion": 1.5
},
{
"id": "c1acbc98-93e5-4215-ba3c-17df88a86903",
"name": "VALIDATE INPUT",
"type": "n8n-nodes-base.if",
"position": [
752,
1200
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1",
"operator": {
"type": "string",
"operation": "notEmpty"
},
"leftValue": "={{ $('PREPARE INPUT').item.json.prompt }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "cde767cf-e7fe-4b52-bb42-934dc9d3daa5",
"name": "RESPOND WITH 400",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
752,
1408
],
"parameters": {
"options": {
"responseCode": 400
},
"respondWith": "json",
"responseBody": "{\"error\": \"Missing required field: prompt\"}"
},
"typeVersion": 1.5
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": true,
"executionOrder": "v1"
},
"versionId": "1f994af0-15c5-46b9-a4b0-23cb20aedddd",
"nodeGroups": [],
"connections": {
"CONFIG": {
"main": [
[
{
"node": "VALIDATE AUTH",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "CONFIG",
"type": "main",
"index": 0
}
]
]
},
"SPLIT TAGS": {
"main": [
[
{
"node": "UPSERT TAG",
"type": "main",
"index": 0
}
]
]
},
"UPSERT TAG": {
"main": [
[
{
"node": "COLLECT TAG IDS",
"type": "main",
"index": 0
}
]
]
},
"CALL WRITER": {
"main": [
[
{
"node": "BUILD REVIEW PROMPT",
"type": "main",
"index": 0
}
],
[
{
"node": "HANDLE PIPELINE FAILURE",
"type": "main",
"index": 0
}
]
]
},
"DECODE IMAGE": {
"main": [
[
{
"node": "UPLOAD IMAGE TO WORDPRESS",
"type": "main",
"index": 0
}
],
[
{
"node": "SET NO IMAGE",
"type": "main",
"index": 0
}
]
]
},
"SET MEDIA ID": {
"main": [
[
{
"node": "MERGE IMAGE RESULT",
"type": "main",
"index": 0
}
]
]
},
"SET NO IMAGE": {
"main": [
[
{
"node": "MERGE IMAGE RESULT",
"type": "main",
"index": 1
}
]
]
},
"CALL REVIEWER": {
"main": [
[
{
"node": "PARSE ARTICLE",
"type": "main",
"index": 0
}
],
[
{
"node": "HANDLE PIPELINE FAILURE",
"type": "main",
"index": 0
}
]
]
},
"PARSE ARTICLE": {
"main": [
[
{
"node": "SYNC BRANCHES",
"type": "main",
"index": 1
}
]
]
},
"PARSE OUTLINE": {
"main": [
[
{
"node": "CHECK IMAGE ENABLED",
"type": "main",
"index": 0
},
{
"node": "FETCH RECENT POSTS",
"type": "main",
"index": 0
}
]
]
},
"PREPARE INPUT": {
"main": [
[
{
"node": "VALIDATE INPUT",
"type": "main",
"index": 0
}
]
]
},
"SYNC BRANCHES": {
"main": [
[
{
"node": "SPLIT TAGS",
"type": "main",
"index": 0
}
]
]
},
"VALIDATE AUTH": {
"main": [
[
{
"node": "PREPARE INPUT",
"type": "main",
"index": 0
}
],
[
{
"node": "REJECT REQUEST",
"type": "main",
"index": 0
}
]
]
},
"GENERATE IMAGE": {
"main": [
[
{
"node": "DECODE IMAGE",
"type": "main",
"index": 0
}
],
[
{
"node": "SET NO IMAGE",
"type": "main",
"index": 0
}
]
]
},
"VALIDATE INPUT": {
"main": [
[
{
"node": "BUILD OUTLINE PROMPT",
"type": "main",
"index": 0
}
],
[
{
"node": "RESPOND WITH 400",
"type": "main",
"index": 0
}
]
]
},
"CALL RESEARCHER": {
"main": [
[
{
"node": "PARSE OUTLINE",
"type": "main",
"index": 0
}
],
[
{
"node": "HANDLE PIPELINE FAILURE",
"type": "main",
"index": 0
}
]
]
},
"COLLECT TAG IDS": {
"main": [
[
{
"node": "CREATE WORDPRESS POST",
"type": "main",
"index": 0
}
]
]
},
"COLLECT LLM COSTS": {
"main": [
[
{
"node": "SLACK NOTIFICATION",
"type": "main",
"index": 0
}
]
]
},
"SET RANK MATH SEO": {
"main": [
[
{
"node": "CHECK SLACK CHANNEL",
"type": "main",
"index": 0
}
]
]
},
"BUILD DRAFT PROMPT": {
"main": [
[
{
"node": "CALL WRITER",
"type": "main",
"index": 0
}
]
]
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googlePalmApislackOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow accepts an article brief via webhook, routes it through a three-stage LLM pipeline to research, draft, and review HTML content, optionally generates a featured image with Google Imagen, then creates a WordPress draft post with tags and Rank Math SEO metadata and…
Source: https://n8n.io/workflows/16554/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Send an article brief via webhook and get back a fully-written WordPress draft — complete with SEO metadata, a generated featured image, and a Slack notification to your team — using Anthropic Claude,
AI-Generated Summary Block for WordPress Posts - with OpenAI, WordPress, Google Sheets & Slack. Uses httpRequest, googleSheets, slack, wordpress. Event-driven trigger; 32 nodes.
This workflow runs two parallel flows that together create a fully hands-off SEO content pipeline, from topic selection to Google indexing.
This workflow automates the end-to-end process of creating, optimizing, and publishing content on WordPress.
Optimize your WordPress titles and meta descriptions with AI (OpenAI), update them directly in Yoast SEO, log results in Google Sheets, and receive a styled report by email. All from your own n8n inst