{
  "name": "SEO Blog Posts to Contentful Drafts",
  "nodes": [
    {
      "parameters": {},
      "id": "manual-trigger",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        240,
        380
      ]
    },
    {
      "parameters": {
        "mode": "raw",
        "jsonOutput": "{\n  \"topic\": \"AI Agents im Mittelstand sinnvoll einführen\",\n  \"primary_keyword\": \"ai agents im mittelstand\",\n  \"secondary_keywords\": [\"workflow automation\", \"ki prozesse\", \"digitale assistenten\"],\n  \"target_audience\": \"COOs und Operations Leads in wachsenden B2B-Unternehmen\",\n  \"search_intent\": \"informational\",\n  \"brand_voice\": \"klar, praxisnah, optimistisch, ohne Buzzword-Overkill\",\n  \"cta\": \"Buche ein kostenloses Workflow-Scoping mit Happy Agents\",\n  \"desired_length\": 1400,\n  \"contentfulSpaceId\": \"YOUR_CONTENTFUL_SPACE_ID\",\n  \"contentfulEnvironment\": \"master\",\n  \"contentTypeId\": \"blogPost\",\n  \"contentLocale\": \"en-US\",\n  \"reviewStatus\": \"needs_review\",\n  \"slackWebhookUrl\": \"https://hooks.slack.com/services/REPLACE/ME\"\n}"
      },
      "id": "input-briefing",
      "name": "Input Briefing",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        460,
        380
      ]
    },
    {
      "parameters": {
        "language": "JavaScript",
        "jsCode": "const item = $input.first().json;\nconst required = [\n  'topic',\n  'primary_keyword',\n  'secondary_keywords',\n  'target_audience',\n  'search_intent',\n  'brand_voice',\n  'cta',\n  'desired_length',\n  'contentfulSpaceId',\n  'contentfulEnvironment',\n  'contentTypeId',\n  'contentLocale'\n];\nconst missing = required.filter((key) => {\n  const value = item[key];\n  if (Array.isArray(value)) return value.length === 0;\n  return value === undefined || value === null || value === '';\n});\nif (missing.length) {\n  throw new Error(`Missing required fields: ${missing.join(', ')}`);\n}\nreturn [{ json: { ...item, rewrite_attempt: 0 } }];"
      },
      "id": "validate-briefing",
      "name": "Validate Briefing",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        700,
        380
      ]
    },
    {
      "parameters": {
        "language": "JavaScript",
        "jsCode": "const input = $input.first().json;\nconst prompt = [\n  'You are an expert SEO strategist.',\n  'Return valid JSON only with the following keys:',\n  'workingTitle, seoAngle, outline, metaTitle, metaDescription, excerpt, slug.',\n  'The outline must be an array of sections. Each section needs h2 and bullets.',\n  `Topic: ${input.topic}`,\n  `Primary keyword: ${input.primary_keyword}`,\n  `Secondary keywords: ${input.secondary_keywords.join(', ')}`,\n  `Audience: ${input.target_audience}`,\n  `Search intent: ${input.search_intent}`,\n  `Brand voice: ${input.brand_voice}`,\n  `Target length: ${input.desired_length} words`,\n  `CTA: ${input.cta}`\n].join('\\n');\nreturn [{ json: { ...input, outlinePrompt: prompt } }];"
      },
      "id": "build-outline-prompt",
      "name": "Build Outline Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        940,
        380
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"gpt-4.1\",\n  \"response_format\": { \"type\": \"json_object\" },\n  \"messages\": [\n    { \"role\": \"system\", \"content\": \"You create clean SEO blog plans for B2B content teams.\" },\n    { \"role\": \"user\", \"content\": $json.outlinePrompt }\n  ]\n}",
        "options": {}
      },
      "id": "generate-outline",
      "name": "Generate Outline",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1180,
        380
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "OPENAI_HTTP_HEADER_AUTH",
          "name": "OpenAI API"
        }
      }
    },
    {
      "parameters": {
        "language": "JavaScript",
        "jsCode": "const original = $('Build Outline Prompt').first().json;\nconst content = $json.choices?.[0]?.message?.content;\nif (!content) throw new Error('Outline generation returned no content');\nconst parsed = JSON.parse(content);\nreturn [{ json: { ...original, outlineResult: parsed } }];"
      },
      "id": "parse-outline",
      "name": "Parse Outline",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1420,
        380
      ]
    },
    {
      "parameters": {
        "language": "JavaScript",
        "jsCode": "const item = $input.first().json;\nconst prompt = [\n  'Write a complete SEO blog post in markdown.',\n  'Follow the provided outline exactly, use a practical B2B tone, avoid fluff and buzzwords.',\n  'Include a clear intro, useful section content, and a closing CTA.',\n  `Target length: ${item.desired_length} words`,\n  `Primary keyword: ${item.primary_keyword}`,\n  `Secondary keywords: ${item.secondary_keywords.join(', ')}`,\n  `CTA: ${item.cta}`,\n  'Outline JSON:',\n  JSON.stringify(item.outlineResult)\n].join('\\n\\n');\nreturn [{ json: { ...item, articlePrompt: prompt } }];"
      },
      "id": "build-article-prompt",
      "name": "Build Article Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1660,
        380
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"gpt-4.1\",\n  \"messages\": [\n    { \"role\": \"system\", \"content\": \"You are a senior B2B content writer.\" },\n    { \"role\": \"user\", \"content\": $json.articlePrompt }\n  ]\n}",
        "options": {}
      },
      "id": "generate-article",
      "name": "Generate Article",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1900,
        380
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "OPENAI_HTTP_HEADER_AUTH",
          "name": "OpenAI API"
        }
      }
    },
    {
      "parameters": {
        "language": "JavaScript",
        "jsCode": "const seed = $('Build Article Prompt').first().json;\nconst body = $json.choices?.[0]?.message?.content;\nif (!body) throw new Error('Article generation returned no content');\nconst draft = {\n  title: seed.outlineResult.workingTitle,\n  slug: seed.outlineResult.slug,\n  excerpt: seed.outlineResult.excerpt,\n  seoTitle: seed.outlineResult.metaTitle,\n  seoDescription: seed.outlineResult.metaDescription,\n  body,\n  primaryKeyword: seed.primary_keyword,\n  secondaryKeywords: seed.secondary_keywords,\n  status: seed.reviewStatus,\n  rewrite_attempt: seed.rewrite_attempt,\n  contentfulSpaceId: seed.contentfulSpaceId,\n  contentfulEnvironment: seed.contentfulEnvironment,\n  contentTypeId: seed.contentTypeId,\n  contentLocale: seed.contentLocale,\n  slackWebhookUrl: seed.slackWebhookUrl,\n  cta: seed.cta,\n  desired_length: seed.desired_length\n};\nreturn [{ json: draft }];"
      },
      "id": "assemble-draft",
      "name": "Assemble Draft",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2140,
        380
      ]
    },
    {
      "parameters": {
        "language": "JavaScript",
        "jsCode": "const item = $input.first().json;\nconst text = item.body || '';\nconst words = text.trim().split(/\\s+/).filter(Boolean).length;\nconst minWords = Math.floor(item.desired_length * 0.85);\nconst maxWords = Math.ceil(item.desired_length * 1.15);\nconst headings = (text.match(/^##\\s+/gm) || []).length;\nconst failures = [];\nif (!text.toLowerCase().includes(item.primaryKeyword.toLowerCase())) failures.push('Primary keyword missing from body');\nif (headings < 3) failures.push('Need at least three H2 headings');\nif (!text.toLowerCase().includes(item.cta.toLowerCase())) failures.push('CTA missing from article body');\nif (words < minWords || words > maxWords) failures.push(`Word count ${words} outside target range ${minWords}-${maxWords}`);\nif (/todo|tbd|lorem ipsum/i.test(text)) failures.push('Placeholder text detected');\nreturn [{ json: { ...item, qa: { passed: failures.length === 0, failures, words, minWords, maxWords } } }];"
      },
      "id": "qa-checks",
      "name": "QA Checks",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2380,
        380
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json.qa.passed}}",
              "value2": true
            }
          ]
        }
      },
      "id": "qa-passed",
      "name": "QA Passed?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2620,
        380
      ]
    },
    {
      "parameters": {
        "language": "JavaScript",
        "jsCode": "const item = $input.first().json;\nif (item.rewrite_attempt >= 1) {\n  throw new Error(`QA failed after rewrite: ${item.qa.failures.join('; ')}`);\n}\nconst prompt = [\n  'Rewrite the markdown blog post and fix the quality issues listed below.',\n  `Failures: ${item.qa.failures.join('; ')}`,\n  'Keep the title, intent and CTA aligned with the original draft.',\n  'Return only the revised markdown article.',\n  '',\n  item.body\n].join('\\n');\nreturn [{ json: { ...item, rewritePrompt: prompt, rewrite_attempt: item.rewrite_attempt + 1 } }];"
      },
      "id": "build-rewrite-prompt",
      "name": "Build Rewrite Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2860,
        520
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"gpt-4.1\",\n  \"messages\": [\n    { \"role\": \"system\", \"content\": \"You are editing an SEO article to meet strict content requirements.\" },\n    { \"role\": \"user\", \"content\": $json.rewritePrompt }\n  ]\n}",
        "options": {}
      },
      "id": "rewrite-article",
      "name": "Rewrite Article",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3100,
        520
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "OPENAI_HTTP_HEADER_AUTH",
          "name": "OpenAI API"
        }
      }
    },
    {
      "parameters": {
        "language": "JavaScript",
        "jsCode": "const seed = $('Build Rewrite Prompt').first().json;\nconst body = $json.choices?.[0]?.message?.content;\nif (!body) throw new Error('Rewrite generation returned no content');\nreturn [{ json: { ...seed, body } }];"
      },
      "id": "apply-rewrite",
      "name": "Apply Rewrite",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3340,
        520
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.contentful.com/spaces/{{$json.contentfulSpaceId}}/environments/{{$json.contentfulEnvironment}}/entries",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/vnd.contentful.management.v1+json"
            },
            {
              "name": "X-Contentful-Content-Type",
              "value": "={{$json.contentTypeId}}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"fields\": {\n    \"title\": { \"{{$json.contentLocale}}\": $json.title },\n    \"slug\": { \"{{$json.contentLocale}}\": $json.slug },\n    \"excerpt\": { \"{{$json.contentLocale}}\": $json.excerpt },\n    \"seoTitle\": { \"{{$json.contentLocale}}\": $json.seoTitle },\n    \"seoDescription\": { \"{{$json.contentLocale}}\": $json.seoDescription },\n    \"body\": { \"{{$json.contentLocale}}\": $json.body },\n    \"primaryKeyword\": { \"{{$json.contentLocale}}\": $json.primaryKeyword },\n    \"secondaryKeywords\": { \"{{$json.contentLocale}}\": $json.secondaryKeywords },\n    \"status\": { \"{{$json.contentLocale}}\": $json.status }\n  }\n}",
        "options": {}
      },
      "id": "create-contentful-draft",
      "name": "Create Contentful Draft",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2860,
        240
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "CONTENTFUL_HTTP_HEADER_AUTH",
          "name": "Contentful Management API"
        }
      }
    },
    {
      "parameters": {
        "language": "JavaScript",
        "jsCode": "const request = $('Create Contentful Draft').first().json;\nconst source = $('QA Passed?').first().json;\nconst entryId = request.sys?.id;\nif (!entryId) throw new Error('Contentful did not return an entry id');\nconst editorUrl = `https://app.contentful.com/spaces/${source.contentfulSpaceId}/environments/${source.contentfulEnvironment}/entries/${entryId}`;\nreturn [{ json: { ...source, contentfulEntryId: entryId, editorUrl } }];"
      },
      "id": "prepare-review-message",
      "name": "Prepare Review Message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3100,
        240
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{$json.slackWebhookUrl}}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"text\": `Neuer Blogpost-Draft bereit fuer Review\\nTitel: ${$json.title}\\nPrimary keyword: ${$json.primaryKeyword}\\nExcerpt: ${$json.excerpt}\\nEditor: ${$json.editorUrl}`\n}",
        "options": {}
      },
      "id": "notify-review-team",
      "name": "Notify Review Team",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3340,
        240
      ]
    },
    {
      "parameters": {
        "mode": "raw",
        "jsonOutput": "={\n  \"status\": \"draft_created\",\n  \"title\": $json.title,\n  \"contentfulEntryId\": $json.contentfulEntryId,\n  \"editorUrl\": $json.editorUrl,\n  \"primaryKeyword\": $json.primaryKeyword\n}"
      },
      "id": "return-success-payload",
      "name": "Return Success Payload",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3580,
        240
      ]
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Input Briefing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Input Briefing": {
      "main": [
        [
          {
            "node": "Validate Briefing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Briefing": {
      "main": [
        [
          {
            "node": "Build Outline Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Outline Prompt": {
      "main": [
        [
          {
            "node": "Generate Outline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Outline": {
      "main": [
        [
          {
            "node": "Parse Outline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Outline": {
      "main": [
        [
          {
            "node": "Build Article Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Article Prompt": {
      "main": [
        [
          {
            "node": "Generate Article",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Article": {
      "main": [
        [
          {
            "node": "Assemble Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assemble Draft": {
      "main": [
        [
          {
            "node": "QA Checks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "QA Checks": {
      "main": [
        [
          {
            "node": "QA Passed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "QA Passed?": {
      "main": [
        [
          {
            "node": "Create Contentful Draft",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Rewrite Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Rewrite Prompt": {
      "main": [
        [
          {
            "node": "Rewrite Article",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rewrite Article": {
      "main": [
        [
          {
            "node": "Apply Rewrite",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Apply Rewrite": {
      "main": [
        [
          {
            "node": "QA Checks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Contentful Draft": {
      "main": [
        [
          {
            "node": "Prepare Review Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Review Message": {
      "main": [
        [
          {
            "node": "Notify Review Team",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify Review Team": {
      "main": [
        [
          {
            "node": "Return Success Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "active": false,
  "versionId": "blogpost-contentful-draft-v1",
  "tags": [
    {
      "name": "content",
      "id": "content-tag"
    },
    {
      "name": "contentful",
      "id": "contentful-tag"
    },
    {
      "name": "seo",
      "id": "seo-tag"
    }
  ]
}
