This workflow corresponds to n8n.io template #8083 — we link there as the canonical source.
This workflow follows the Error Trigger → Gmail 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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "45e081e9-db15-4808-9c26-6f1089f23a0e",
"name": "Cron (Schedule)",
"type": "n8n-nodes-base.cron",
"position": [
-1648,
-304
],
"parameters": {
"triggerTimes": {
"item": [
{
"hour": 19
}
]
}
},
"typeVersion": 1
},
{
"id": "7e72fddb-7c81-4bf3-bd96-b02c3ebb53efa",
"name": "Set (Configs)",
"type": "n8n-nodes-base.set",
"position": [
-1424,
-304
],
"parameters": {
"values": {
"string": [
{
"name": "NOTION_DB_ID",
"value": "25496301-7675-81fa-9d31-cedcc7f452c6a"
},
{
"name": "NOTION_PAGE_PARENT",
"value": "XXXXXXXXXXXXXXXXXXXXXXXXXXX"
},
{
"name": "LINKEDIN_PERSON_URN",
"value": "urn:li:person:XXXXXXXX"
},
{
"name": "OPENAI_MODEL",
"value": "gpt-4.1-mini"
},
{
"name": "EMAIL_TO",
"value": "user@example.com"
}
],
"boolean": [
{
"name": "LINKEDIN_PUBLISH",
"value": true
}
]
},
"options": {},
"keepOnlySet": true
},
"typeVersion": 2
},
{
"id": "648ff7d2-a55e-4e0c-8b12-d3634d1f1b958",
"name": "Notion \u2192 Query Ideas DB (all)",
"type": "n8n-nodes-base.notion",
"position": [
-1200,
-304
],
"parameters": {
"simple": false,
"options": {
"sort": {
"sortValue": [
{
"key": "created_time",
"direction": "ascending",
"timestamp": true
}
]
}
},
"resource": "databasePage",
"operation": "getAll",
"returnAll": true,
"databaseId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.NOTION_DB_ID }}"
},
"filterJson": "={\n \"property\": \"published\",\n \"checkbox\": { \"equals\": false }\n}",
"filterType": "json"
},
"credentials": {
"notionApi": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "c964ef88-6dea-4ea1-ad6c-2eef99b483646",
"name": "IF (Has Eligible?)",
"type": "n8n-nodes-base.if",
"position": [
-1200,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d6c0245f-b296-4a04-861d-84c42736aa997",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.eligible_count > 0 }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2
},
{
"id": "e3a428a0-c52e-47f6-9281-1063f84e587c3",
"name": "Split In Batches (1)",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-656,
-304
],
"parameters": {
"options": {},
"batchSize": 1
},
"typeVersion": 2
},
{
"id": "017d6b4c-52f8-4238-abf1-95f44d08b182b",
"name": "Error Trigger (Any Node Fails)",
"type": "n8n-nodes-base.errorTrigger",
"position": [
-1520,
2272
],
"parameters": {},
"typeVersion": 1
},
{
"id": "219ddd0a-9ad8-4555-acf1-b002406738346",
"name": "Count Eligible",
"type": "n8n-nodes-base.code",
"position": [
-1424,
0
],
"parameters": {
"jsCode": "// Emit a single item with the eligible count for IF routing\nreturn [{ json: { eligible_count: items.length } }];"
},
"typeVersion": 2
},
{
"id": "7d00232e-be9d-4cdc-b072-eedc64dd79e3c",
"name": "Filter Unpublished + Map",
"type": "n8n-nodes-base.code",
"position": [
-976,
-304
],
"parameters": {
"jsCode": "/* Filter unpublished + map fields safely */\nconst out = [];\nfor (const item of items) {\n const page = item.json;\n const id = page.id;\n const p = page.properties || {};\n const g = (prop) => {\n if (!prop) return '';\n if (prop.type === 'title' && prop.title?.length) return prop.title.map(t=>t.plain_text).join('');\n if (prop.type === 'rich_text' && prop.rich_text?.length) return prop.rich_text.map(t=>t.plain_text).join('');\n if (prop.type === 'url') return prop.url || '';\n if (prop.type === 'checkbox') return !!prop.checkbox;\n if (prop.type === 'multi_select') return prop.multi_select.map(o=>o.name);\n if (prop.type === 'select') return prop.select?.name || '';\n if (prop.type === 'date') return prop.date?.start || '';\n return '';\n };\n const isPublished = p.published ? !!p.published.checkbox : false;\n if (isPublished) continue;\n\n const title = g(p.title) || page.properties?.Name?.title?.[0]?.plain_text || '';\n const angle = g(p.angle);\n const tags = p.tags ? (p.tags.type === 'multi_select' ? p.tags.multi_select.map(o=>o.name) : g(p.tags)) : '';\n const primary_link = g(p.primary_link);\n const reference_links = g(p.reference_links);\n const images = g(p.images);\n const notes = g(p.notes);\n const target_audience = g(p.target_audience);\n const canonical_url = g(p.canonical_url);\n const language = g(p.language);\n const slug = g(p.slug);\n\n out.push({ json: {\n notion_page_id: id,\n title,\n angle,\n tags,\n primary_link,\n reference_links,\n images,\n notes,\n target_audience,\n canonical_url,\n language,\n slug\n }});\n}\nreturn out;\n"
},
"typeVersion": 2
},
{
"id": "40b826f5-b1ef-490b-b208-f1754c1972d81",
"name": "Send a message",
"type": "n8n-nodes-base.gmail",
"position": [
-976,
0
],
"parameters": {
"sendTo": "={{ $('Set (Configs)').item.json.EMAIL_TO }}",
"message": "=No rows found in {{ $('Set (Configs)').item.json.NOTION_DB_ID }} with published != true at {{$now}}.",
"options": {},
"subject": "Blog Pipeline: No Eligible Notion Rows!",
"emailType": "text"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "87a55a66-d609-47d2-a177-eddb4a7d9d560",
"name": "Parse & Validate JSON",
"type": "n8n-nodes-base.code",
"position": [
144,
-304
],
"parameters": {
"jsCode": "/**\n * Node 11 \u2014 Function (Parse & Validate JSON from OpenAI)\n * Soft-handles safety_flags (warnings) instead of throwing.\n */\n\n// ---------- 1) Extract message.content ----------\nconst input = items[0].json;\n\nfunction extractContent(nodeOut) {\n // n8n OpenAI nodes vary; support a few shapes:\n\n // A) Array of { message: { content } }\n if (Array.isArray(nodeOut) && nodeOut[0]?.message?.content !== undefined) {\n return nodeOut[0].message.content;\n }\n // B) Chat Completions\n if (nodeOut?.data?.choices?.[0]?.message?.content !== undefined) {\n return nodeOut.data.choices[0].message.content;\n }\n // C) OpenAI node returning raw string JSON at top level\n if (typeof nodeOut === \"string\") {\n return nodeOut;\n }\n // D) Flattened shape\n if (nodeOut?.message?.content !== undefined) {\n return nodeOut.message.content;\n }\n // E) Sometimes models already return parsed object\n if (nodeOut?.content !== undefined) {\n return nodeOut.content;\n }\n throw new Error(\"Node11: could not locate message.content from OpenAI output\");\n}\n\nlet content = extractContent(input);\n\n// ---------- 2) Parse to object ----------\nlet parsed;\nif (content && typeof content === \"object\") {\n parsed = content;\n} else if (typeof content === \"string\") {\n let txt = content.trim();\n if (txt.startsWith(\"```\")) {\n txt = txt.replace(/^```(?:json)?\\s*/i, \"\").replace(/\\s*```$/i, \"\");\n }\n txt = txt\n .replace(/[\\u2018\\u2019]/g, \"'\")\n .replace(/[\\u201C\\u201D]/g, '\"')\n .replace(/[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F]/g, \" \")\n .replace(/,\\s*([}\\]])/g, \"$1\");\n\n // escape raw newlines inside strings\n (function escapeNewlinesInStrings() {\n let out = \"\", inStr = false, esc = false;\n for (let i = 0; i < txt.length; i++) {\n const ch = txt[i];\n if (!inStr) { if (ch === '\"') inStr = true; out += ch; continue; }\n if (esc) { out += ch; esc = false; continue; }\n if (ch === \"\\\\\") { out += ch; esc = true; continue; }\n if (ch === '\"') { inStr = false; out += ch; continue; }\n if (ch === \"\\n\" || ch === \"\\r\") { out += \"\\\\n\"; continue; }\n out += ch;\n }\n txt = out;\n })();\n\n try {\n parsed = JSON.parse(txt);\n } catch (e) {\n throw new Error(\"Node11: JSON.parse failed after repair: \" + e.message);\n }\n} else {\n throw new Error(\"Node11: message.content is neither object nor string\");\n}\n\n// ---------- 3) Validate + normalize ----------\nconst errs = [];\nconst warn = [];\nconst isNonEmpty = (s) => typeof s === \"string\" && s.trim().length > 0;\nconst arrOfStr = (a) => Array.isArray(a) ? a.map(x => String(x)).filter(Boolean) : [];\n\nif (!parsed.blog) errs.push(\"blog missing\");\nelse {\n if (!isNonEmpty(parsed.blog.title)) errs.push(\"blog.title missing\");\n if (!isNonEmpty(parsed.blog.slug)) errs.push(\"blog.slug missing\");\n if (!isNonEmpty(parsed.blog.markdown)) errs.push(\"blog.markdown missing\");\n if (parsed.blog.slug && parsed.blog.slug.length > 100) errs.push(\"blog.slug > 100 chars\");\n parsed.blog.keywords = arrOfStr(parsed.blog.keywords);\n parsed.blog.image_alt_texts = arrOfStr(parsed.blog.image_alt_texts);\n if (\n parsed.blog.canonical_url !== null &&\n parsed.blog.canonical_url !== undefined &&\n typeof parsed.blog.canonical_url !== \"string\"\n ) errs.push(\"blog.canonical_url must be string or null\");\n}\n\nif (!parsed.linkedin) errs.push(\"linkedin missing\");\nelse {\n if (!isNonEmpty(parsed.linkedin.final_text)) errs.push(\"linkedin.final_text missing\");\n // hashtags soft window\n parsed.linkedin.hashtags = arrOfStr(parsed.linkedin.hashtags);\n if (parsed.linkedin.hashtags.length > 8) {\n parsed.linkedin.hashtags = parsed.linkedin.hashtags.slice(0, 8);\n }\n // char guard\n let ft = String(parsed.linkedin.final_text || \"\");\n if (ft.length > 1200) {\n let cut = ft.slice(0, 1180);\n const lastBreak = Math.max(cut.lastIndexOf(\"\\n\"), cut.lastIndexOf(\".\"));\n if (lastBreak > 800) cut = cut.slice(0, lastBreak + 1);\n parsed.linkedin.final_text = cut;\n warn.push(\"linkedin.final_text truncated to 1200 chars\");\n }\n}\n\n// Safety flags: treat as warnings (expose downstream)\nif (!Array.isArray(parsed.safety_flags)) parsed.safety_flags = [];\nif (parsed.safety_flags.length) {\n warn.push(\"safety_flags present: \" + parsed.safety_flags.join(\", \"));\n}\n\n// Hard errors?\nif (errs.length) {\n throw new Error(\"Node11 validation failed: \" + errs.join(\"; \"));\n}\n\n// ---------- 4) Merge with Normalize Arrays (Node 9), return ----------\nlet norm = {};\ntry {\n const n = $items(\"Function (Normalize Arrays)\", 0, $runIndex);\n if (Array.isArray(n) && n[0] && n[0].json) norm = n[0].json;\n} catch (_) {}\n\nreturn [{\n json: {\n ...norm,\n openai: parsed,\n warnings: warn\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "3ab2103a-a358-415c-a4d1-1df7c3cb14f5",
"name": "Normalize Arrays",
"type": "n8n-nodes-base.code",
"position": [
-432,
-304
],
"parameters": {
"jsCode": "// Normalize arrays + carry configs\nfunction toArray(v){\n if (Array.isArray(v)) return v;\n if (typeof v === 'string'){\n const s = v.trim();\n if (!s) return [];\n try { const parsed = JSON.parse(s); if (Array.isArray(parsed)) return parsed; } catch(e) {}\n return s.split(',').map(x=>x.trim()).filter(Boolean);\n }\n return [];\n}\n\n// Pull config from the Set (Configs) node\n// Simple form:\nconst cfg = $node[\"Set (Configs)\"].json;\n// Robust form (if you prefer):\n// const cfg = $items(\"Set (Configs)\", 0, $runIndex)[0].json;\n\nconst i = items[0].json;\n\nreturn [{\n json: {\n NOTION_DB_ID: cfg.NOTION_DB_ID,\n NOTION_PAGE_PARENT: cfg.NOTION_PAGE_PARENT,\n LINKEDIN_PERSON_URN: cfg.LINKEDIN_PERSON_URN,\n OPENAI_MODEL: cfg.OPENAI_MODEL,\n EMAIL_TO: cfg.EMAIL_TO,\n\n notion_page_id: i.notion_page_id,\n title: i.title || '',\n angle: i.angle || '',\n tags: toArray(i.tags),\n primary_link: i.primary_link || '',\n reference_links: toArray(i.reference_links),\n images: toArray(i.images),\n notes: i.notes || '',\n target_audience: i.target_audience || '',\n canonical_url: i.canonical_url || '',\n language: i.language || '',\n slug: i.slug || ''\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "52a1ad9c-cc6a-49a1-ab7f-9b3a8a2e8c2b",
"name": "Create a post",
"type": "n8n-nodes-base.linkedIn",
"position": [
1872,
-432
],
"parameters": {
"text": "={{ $('Content Creator').item.json.message.content.linkedin.final_text }}",
"person": "k47kCb899q",
"additionalFields": {
"title": "={{ $('Content Creator').item.json.message.content.blog.title }}",
"visibility": "PUBLIC"
},
"binaryPropertyName": "=data",
"shareMediaCategory": "IMAGE"
},
"credentials": {
"linkedInOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "128ee066-dca2-4c19-830b-621e3eafbbc0",
"name": "Create a Article Database Page",
"type": "n8n-nodes-base.notion",
"position": [
432,
-304
],
"parameters": {
"title": "={{ $json.openai.blog.title }}",
"simple": false,
"options": {},
"resource": "databasePage",
"databaseId": {
"__rl": true,
"mode": "list",
"value": "25496301-7675-81bc-b023-c7e012094120",
"cachedResultUrl": "https://www.notion.so/25496301767581bcb023c7e012094120",
"cachedResultName": "Articles"
},
"propertiesUi": {
"propertyValues": [
{
"key": "title|title",
"title": "={{ $json.openai.blog.title }}"
},
{
"key": "slug|rich_text",
"text": {
"text": [
{
"text": "={{ $json.openai.blog.slug }}",
"annotationUi": {}
}
]
},
"richText": true
},
{
"key": "excerpt|rich_text",
"text": {
"text": [
{
"text": "={{ $json.openai.blog.excerpt }}",
"annotationUi": {}
}
]
},
"richText": true
},
{
"key": "keywords|multi_select",
"multiSelectValue": "={{ $json.openai.blog.keywords }}"
},
{
"key": "canonical_url|url",
"urlValue": "={{ $json.openai.blog.canonical_url || '' }}",
"ignoreIfEmpty": true
},
{
"key": "suggested_cover_caption|rich_text",
"text": {
"text": [
{
"text": "={{ $json.openai.blog.suggested_cover_caption }}",
"annotationUi": {}
}
]
},
"richText": true
},
{
"key": "image_alt_texts|rich_text",
"text": {
"text": [
{
"text": "={{ $json.openai.blog.image_alt_texts.join(', ') }}",
"annotationUi": {}
}
]
},
"richText": true
},
{
"key": "language|select",
"selectValue": "={{ $('Split In Batches (1)').item.json.language || 'en' }}"
},
{
"key": "tags|multi_select",
"multiSelectValue": "={{ $json.openai.linkedin.hashtags }}"
},
{
"key": "status|select",
"selectValue": "Published"
},
{
"key": "published_at|date",
"date": "={{ $now }}",
"timezone": "Asia/Kathmandu"
},
{
"key": "source_idea|relation",
"relationValue": [
"={{ $('Split In Batches (1)').item.json.notion_page_id }}"
]
},
{
"key": "linkedin_draft|rich_text",
"text": {
"text": [
{
"text": "={{ $json.openai.linkedin.final_text }}",
"annotationUi": {}
}
]
},
"richText": true
}
]
}
},
"credentials": {
"notionApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "6160be76-97f5-4f4c-bfa6-5e480080d854",
"name": "Draft Email",
"type": "n8n-nodes-base.gmail",
"position": [
1120,
-80
],
"parameters": {
"sendTo": "={{ $('Set (Configs)').item.json.EMAIL_TO }}",
"message": "=Title: {{ $json.properties.title.title[0].text.content }}\nDraft: {{ $json.properties.linkedin_draft.rich_text[0].text.content }}",
"options": {},
"subject": "LinkedIn Draft Ready",
"emailType": "text"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "a1a7482f-3868-402c-a05b-2535b8443ad7",
"name": "Published Email",
"type": "n8n-nodes-base.gmail",
"position": [
2096,
-432
],
"parameters": {
"sendTo": "user@example.com",
"message": "=Published: {{ $('Create a Article Database Page').item.json.properties.title.title[0].text.content }}\nSlug: {{ $('Create a Article Database Page').item.json.properties.slug.rich_text[0].text.content }}\nLinkedIn: posted as person.\nIdea Page: {{ $('Create a Article Database Page').item.json.id }}",
"options": {},
"subject": "LinkedIn Post Published!",
"emailType": "text"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "a7b80170-9832-46fb-85ba-8b87c5bd9da4",
"name": "Draft Update Database",
"type": "n8n-nodes-base.notion",
"position": [
1344,
-80
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Create a Article Database Page').item.json.id }}"
},
"simple": false,
"options": {},
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "status|select",
"selectValue": "Draft"
}
]
}
},
"credentials": {
"notionApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "4e43c32b-407c-4a62-8a0d-2b5eea10c615",
"name": "Update a database page",
"type": "n8n-nodes-base.notion",
"position": [
2320,
-432
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Create a Article Database Page').item.json.id }}"
},
"simple": false,
"options": {},
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "status|select",
"selectValue": "Published"
}
]
}
},
"credentials": {
"notionApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "c3dfe348-0a68-45ee-b538-2f72fdbffaa9",
"name": "Download Image",
"type": "n8n-nodes-base.httpRequest",
"position": [
1648,
-432
],
"parameters": {
"url": "={{ $json.output[0] }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "4b21b649-4087-4ef2-aa8d-9ee53c5db581",
"name": "Replicate Image Prediction",
"type": "n8n-nodes-base.httpRequest",
"position": [
1424,
-432
],
"parameters": {
"url": "https://api.replicate.com/v1/models/black-forest-labs/flux-schnell/predictions",
"method": "POST",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
},
"jsonBody": "={\n \"input\": {\n \"prompt\": \"{{ $json.message.content }}\",\n \"aspect_ratio\": \"16:9\"\n }\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"headerParameters": {
"parameters": [
{
"name": "Prefer",
"value": "wait"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"credentials": {
"httpBearerAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "2dd65c1f-d859-4809-857b-7f8b6e535306",
"name": "Content Creator",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
-208,
-304
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-5-mini",
"cachedResultName": "GPT-5-MINI"
},
"options": {},
"messages": {
"values": [
{
"content": "=You are a senior technical writer and LinkedIn ghostwriter for a Nepal-based DevOps/CloudOps/SysOps/Python developer who also loves AI/ML. \nYour job is to take one Notion row and turn it into:\n\n1) A polished, human-readable blog article for Hashnode (Markdown only).\n2) A LinkedIn post that feels authentic, insightful, and share-worthy, and most-importantly simpler english.\n\nYour voice should feel like a real engineer sharing something useful:\n- Informative but conversational\n- Confident but humble\n- Sometimes witty, but never forced\n- Includes actionable value (tips, insights, commands, lessons)\n- Occasionally adds light Nepali flavor naturally if language=\"ne\"\n\n---\n### INPUT\n- title: {{ $json.title }}\n- angle: {{ $json.angle }}\n- tags: {{ $json.tags }}\n- primary_link: {{ $json.primary_link }}\n- reference_links: {{ $json.reference_links }}\n- images: {{ $json.images }}\n- notes: {{ $json.notes }}\n- target_audience: {{ $json.target_audience }}\n- canonical_url: {{ $json.canonical_url }}\n- language: {{ $json.language }}\n- slug: {{ $json.slug }}\n\n---\n### PREFERENCES\n- LinkedIn posts \u2264 1200 characters\n- Blog word count:\n - Rich notes/tags: 900\u20131400 words\n - Sparse notes/tags: 600\u2013900 words\n- Include at least one specific example, command, or actionable tip\n- Only one link allowed in LinkedIn text (if primary_link exists)\n\n---\n### STYLE TARGETS\nFor LinkedIn, pick the most natural style automatically:\n- **Story:** Small anecdote or lesson\n- **Industry Take:** Commentary with reflection\n- **Tech Breakdown:** Clear step-by-step with code/tips\n- **Basics:** Foundational guidance in checklist format\n- **Humor List:** Funny but relatable \"few techs\" overwhelm joke\n- **Optimization/Win:** Real-world result or improvement shared\n\n---\n### BLOG REQUIREMENTS\n- Markdown only, no frontmatter\n- Structure:\n - Hook intro (2\u20134 lines)\n - Clear sections with H2/H3\n - Include steps, code snippets, or bullet lists\n - Add \u201cSharp edges & gotchas\u201d with realistic pitfalls\n - Include a mini case/example\n - End with actionable \u201cKey Takeaways\u201d (3\u20136 bullets)\n- Include:\n - SEO: slug, excerpt, keywords (5\u201310)\n - Suggested cover caption\n - Alt text suggestions for images\n - Canonical URL if provided\n\n---\n### LINKEDIN REQUIREMENTS\n- Start with a strong HOOK (1\u20132 lines), do not mention word itself like 'hook' or similar\n- Use short, human paragraphs\n- Include at least one actionable tip or fact\n- Invite engagement with a closing question or CTA\n- Add 5\u201310 hashtags, a mix of broad (#DevOps #Cloud) and niche (#AWS #Kubernetes #Observability)\n\n---\n### RULES\n- Never invent unknown facts\n- Prioritize clear, human language\n- Favor real-world examples over generic advice\n- Use emojis sparingly and purposefully (max 3)\n\n---\n### OUTPUT SCHEMA\n{\n \"blog\": {\n \"title\": \"string\",\n \"slug\": \"string\",\n \"excerpt\": \"string\",\n \"keywords\": [\"string\", \"...\"],\n \"canonical_url\": \"string|null\",\n \"suggested_cover_caption\": \"string\",\n \"image_alt_texts\": [\"string\", \"...\"],\n \"markdown\": \"string\"\n },\n \"linkedin\": {\n \"style_used\": \"story|industry_take|tech_breakdown|basics|humor_list|optimization\",\n \"final_text\": \"string\",\n \"alt_titles\": [\"string\",\"string\",\"string\"]\n },\n \"safety_flags\": []\n}"
}
]
},
"simplify": "={{ true }}",
"jsonOutput": true
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.8
},
{
"id": "17809fd9-84d8-458b-a904-b497549c10dd",
"name": "Image prompt generator",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
1072,
-432
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini",
"cachedResultName": "GPT-4.1-MINI"
},
"options": {},
"messages": {
"values": [
{
"content": "={\n \"title\": \"{{$json.properties?.title?.title?.[0]?.plain_text || ''}}\",\n \"excerpt\": \"{{$json.properties?.excerpt?.rich_text?.[0]?.plain_text || ''}}\",\n \"linkedin_draft\": \"{{$json.properties?.linkedin_draft?.rich_text?.[0]?.plain_text || ''}}\",\n \"alt\": \"{{$json.properties?.image_alt_texts?.rich_text?.[0]?.plain_text || ''}}\",\n \"keywords\": \"{{$json.properties?.keywords?.multi_select?.map(k => k.name).join(', ') || ''}}\"\n}\n"
},
{
"role": "system",
"content": "You generate a single, realistic text-to-image prompt for the Replicate model black-forest-labs/flux-schnell, based on details from a LinkedIn post.\n\nRULES:\n- Output only the prompt as plain text, no JSON, no commentary.\n- Make the scene look photorealistic but clean and professional \u2014 perfect for LinkedIn.\n- Focus on key objects, actions, and environment described in the post (title, excerpt, alt, keywords).\n- Include subtle realistic elements (lighting, depth, perspective, color) but keep the layout minimal and balanced.\n- Never include any legible text, logos, watermarks, brand names, or faces.\n- Use soft professional lighting, shallow depth of field, and natural tones.\n- Keep it under ~900 characters.\n- Default to a 16:9 aspect ratio.\n"
}
]
}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.8
},
{
"id": "20246ca2-9171-4ba8-8009-f43a43296c59",
"name": "Failure Message",
"type": "n8n-nodes-base.gmail",
"position": [
-1232,
2272
],
"parameters": {
"sendTo": "={{$json.execution?.error?.workflow?.data?.startData?.runData?.['Set (Configs)']?.[0]?.json?.EMAIL_TO || 'test@mail.com'}}",
"message": "=Node: {{$json.execution?.error?.node?.name}}\\nMessage: {{$json.execution?.error?.message}}\\nStack: {{$json.execution?.error?.stack}}\\nLast Payload: {{JSON.stringify($json.execution?.error?.node?.data, null, 2).slice(0,5000)}}",
"options": {},
"subject": "=Blog Pipeline ERROR: {{$json.execution?.error?.message || 'Unknown error'}}",
"emailType": "text"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "efd5db98-aa5e-46e4-9d6c-4708b8db08a4",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1712,
-624
],
"parameters": {
"color": 4,
"width": 960,
"height": 832,
"content": "# INITIALIZATION & DATA COLLECTION\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n- Cron triggers daily at 7 PM\n- Set configs (DB IDs, email, LinkedIn settings)\n- Query Notion for unpublished ideas\n- Filter & clean the data\n- Count eligible posts\n\nIf no posts found \u2192 Send \"nothing to do\" email"
},
"typeVersion": 1
},
{
"id": "0c0f92c8-d1b4-4e11-86cf-dc32022e984b",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-720,
-624
],
"parameters": {
"color": 4,
"width": 1024,
"height": 832,
"content": "# CONTENT PROCESSING & AI GENERATION\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n- Process ideas one by one (batch size: 1)\n- Normalize data for AI prompt\n- OpenAI creates:\n - Full blog post (Markdown)\n - LinkedIn post with hashtags\n - SEO keywords & metadata\n- Validate & parse AI output\n\nQuality control ensures perfect formatting"
},
"typeVersion": 1
},
{
"id": "69e0c3a8-1239-4283-8c64-7c8aa8c3ec4e",
"name": "If LinkedIn Published Check",
"type": "n8n-nodes-base.if",
"position": [
640,
-304
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "58a827f7-5153-4641-afbd-9bdf3d62ee2c",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $('Set (Configs)').item.json.LINKEDIN_PUBLISH || false }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.2,
"alwaysOutputData": false
},
{
"id": "b3f96e1b-73d8-4153-84ef-952fd9785888",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
336,
-624
],
"parameters": {
"color": 5,
"width": 608,
"height": 832,
"content": "# CONTENT STORAGE & PUBLISH DECISION\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n- Save AI content to Articles database\n- Check LINKEDIN_PUBLISH setting\n\nTwo paths:\nPUBLISH = TRUE \u2192 Auto-publish flow\nPUBLISH = FALSE \u2192 Draft review flow\n\nAll content safely stored in Notion first"
},
"typeVersion": 1
},
{
"id": "c663ee05-545d-4c1e-995d-26ed92ecba6d",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
976,
-624
],
"parameters": {
"color": 5,
"width": 1536,
"height": 832,
"content": ""
},
"typeVersion": 1
},
{
"id": "995fee04-6be5-4b7b-ad46-6a0965696e5c",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1920,
-144
],
"parameters": {
"color": 5,
"width": 592,
"height": 352,
"content": "# PUBLISHING & COMPLETION\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\nAUTO-PUBLISH PATH:\n- Generate image prompt \u2192 Create image\n- Post to LinkedIn with image\n- Send success email\n- Mark as \"Published\"\n\nDRAFT PATH:\n- Email draft for review\n- Mark as \"Draft\"\n\nBoth paths ensure proper status tracking"
},
"typeVersion": 1
}
],
"connections": {
"Draft Email": {
"main": [
[
{
"node": "Draft Update Database",
"type": "main",
"index": 0
}
]
]
},
"Create a post": {
"main": [
[
{
"node": "Published Email",
"type": "main",
"index": 0
}
]
]
},
"Set (Configs)": {
"main": [
[
{
"node": "Notion \u2192 Query Ideas DB (all)",
"type": "main",
"index": 0
}
]
]
},
"Count Eligible": {
"main": [
[
{
"node": "IF (Has Eligible?)",
"type": "main",
"index": 0
}
]
]
},
"Download Image": {
"main": [
[
{
"node": "Create a post",
"type": "main",
"index": 0
}
]
]
},
"Send a message": {
"main": [
[]
]
},
"Content Creator": {
"main": [
[
{
"node": "Parse & Validate JSON",
"type": "main",
"index": 0
}
]
]
},
"Cron (Schedule)": {
"main": [
[
{
"node": "Set (Configs)",
"type": "main",
"index": 0
}
]
]
},
"Published Email": {
"main": [
[
{
"node": "Update a database page",
"type": "main",
"index": 0
}
]
]
},
"Normalize Arrays": {
"main": [
[
{
"node": "Content Creator",
"type": "main",
"index": 0
}
]
]
},
"IF (Has Eligible?)": {
"main": [
[],
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
]
]
},
"Split In Batches (1)": {
"main": [
[
{
"node": "Normalize Arrays",
"type": "main",
"index": 0
}
]
]
},
"Parse & Validate JSON": {
"main": [
[
{
"node": "Create a Article Database Page",
"type": "main",
"index": 0
}
]
]
},
"Image prompt generator": {
"main": [
[
{
"node": "Replicate Image Prediction",
"type": "main",
"index": 0
}
]
]
},
"Update a database page": {
"main": [
[]
]
},
"Filter Unpublished + Map": {
"main": [
[
{
"node": "Split In Batches (1)",
"type": "main",
"index": 0
},
{
"node": "Count Eligible",
"type": "main",
"index": 0
}
]
]
},
"Replicate Image Prediction": {
"main": [
[
{
"node": "Download Image",
"type": "main",
"index": 0
}
]
]
},
"If LinkedIn Published Check": {
"main": [
[
{
"node": "Image prompt generator",
"type": "main",
"index": 0
}
],
[
{
"node": "Draft Email",
"type": "main",
"index": 0
}
]
]
},
"Create a Article Database Page": {
"main": [
[
{
"node": "If LinkedIn Published Check",
"type": "main",
"index": 0
}
]
]
},
"Error Trigger (Any Node Fails)": {
"main": [
[
{
"node": "Failure Message",
"type": "main",
"index": 0
}
]
]
},
"Notion \u2192 Query Ideas DB (all)": {
"main": [
[
{
"node": "Filter Unpublished + Map",
"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.
gmailOAuth2httpBearerAuthlinkedInOAuth2ApinotionApiopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Daily trigger scans your Notion database for unpublished blog ideas AI generates complete blog posts + engaging LinkedIn content using OpenAI (Blog Posting is not implemented yet) Creates custom images for posts using Replicate's Flux-Schnell AI model Auto-publishes to LinkedIn…
Source: https://n8n.io/workflows/8083/ — 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.
What it is An automated LinkedIn content system that takes a simple form (idea + optional file), generates LinkedIn posts with OpenAI, stores them in Notion, builds Google Slides carousels, and auto-p
This workflow is your personal CEO Brain. Every Saturday night, it automatically collects the past week’s activity across: 📩 Gmail: filters out spam, promos, receipts, etc. 📅 Google Calendar: grabs pa
Manually monitoring Reddit for relevant discussions can be overwhelming. This automation does all the heavy lifting by automatically searching for keywords across selected subreddits or the entire Red
Automate LinkedIn Posts with AI. Uses scheduleTrigger, stickyNote, notion, linkedIn. Scheduled trigger; 11 nodes.
This template is based on the following template. Thank you for the groundwork, Matheus. Store your snippets of text in a Notion table. Each snippet should have an image associated with it (copy + pas