{
  "id": "EmPnnzwJEHxMuJaw",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI Blog Creator with Gemini, Replicate Image, Supabase Publishing & Slack",
  "tags": [],
  "nodes": [
    {
      "id": "e45d33d9-2fc2-476c-b0ff-63d16b30111a",
      "name": "Fetch Industry Trends",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        224,
        -240
      ],
      "parameters": {
        "url": "https://newsapi.org/v2/everything?q=AI automation SaaS&sortBy=publishedAt&pageSize=10",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "httpQueryAuth"
      },
      "credentials": {
        "httpQueryAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "2c641b4b-9ce0-472d-a970-714d6a630f0c",
      "name": "Publish to Supabase",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2496,
        -240
      ],
      "parameters": {
        "url": "https://your.supabase.co/functions/v1/blog-api",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "authentication": "genericCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "title",
              "value": "={{ $json.title }}"
            },
            {
              "name": "slug",
              "value": "={{ $json.slug }}"
            },
            {
              "name": "excerpt",
              "value": "={{ $json.excerpt }}"
            },
            {
              "name": "content",
              "value": "={{ $json.content }}"
            },
            {
              "name": "image_url",
              "value": "={{ $json.image_url }}"
            },
            {
              "name": "published",
              "value": "true"
            }
          ]
        },
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "17fcce85-bdf1-4c21-848a-3ce667e3aab5",
      "name": "Slack Notification",
      "type": "n8n-nodes-base.slack",
      "position": [
        2720,
        -240
      ],
      "parameters": {
        "text": "=\u2705 *New Blog Published!*  \n*Title:* {{$json['title']}}  \n*Slug:* {{$json['slug']}}  \n*URL:* https://yoururl.com/blog/{{$json['slug']}}",
        "channel": "#blog-automation",
        "attachments": [],
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "5ecb7538-6358-4d89-ac55-8e4539f8d42a",
      "name": "Message a model",
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "position": [
        448,
        -240
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "models/gemini-2.5-flash",
          "cachedResultName": "models/gemini-2.5-flash"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=Act as an AI market analyst. Based on these current trends and articles:\n\"{{ $json.articles }}\"\nGenerate 1 trending blog topic ideas relevant to SaaS, AI, Automation, and industry innovation that would benefit our company\u2019s brand authority.\nReturn them in strict JSON with fields: title, description, keywords.\nno exta text, details etc. give me direct json output without pre and post text.\n"
            }
          ]
        }
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "5da2d4ee-d7b0-441a-8523-067a17182c6a",
      "name": "Message a model1",
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "position": [
        800,
        -240
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "models/gemini-2.5-flash",
          "cachedResultName": "models/gemini-2.5-flash"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=You are an expert SEO content strategist. Write a 1200\u20131500 word blog in Markdown on the topic:\n\"{{ $json.content.parts[0].text }}\"\n\nRequirements:\n- Use high-ranking keywords naturally.\n- Include an SEO title, meta description, and excerpt.\n- Use markdown headings, bullet points, and bold keywords.\n- Structure: Introduction \u2192 Insight \u2192 Case Studies \u2192 Conclusion \u2192 CTA.\nOutput JSON:\n{\n  \"title\": \"...\",\n  \"slug\": \"...\",\n  \"excerpt\": \"...\",\n  \"content\": \"...\",\n  \"keywords\": \"...\",\n  \"image_prompt\": \"...\"\n}\n"
            }
          ]
        }
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1369e40f-2008-4fb7-9123-d17b02a7d42d",
      "name": "Code in JavaScript",
      "type": "n8n-nodes-base.code",
      "position": [
        1152,
        -240
      ],
      "parameters": {
        "jsCode": "// --- Ultra-Resilient Gemini Blog Cleaner ---\n// Works with Markdown fences, nested JSON, or plain text\n\nconst raw = $json.content?.parts?.[0]?.text || '';\nif (!raw) throw new Error('Gemini returned empty response.');\n\nlet text = raw.trim();\n\n// 1\ufe0f\u20e3 Strip Markdown fences and language hints\ntext = text\n  .replace(/```json/gi, '')\n  .replace(/```/g, '')\n  .replace(/^#+\\s+/gm, '')\n  .trim();\n\n// 2\ufe0f\u20e3 Find the innermost JSON block\nlet matches = text.match(/\\{[\\s\\S]*\\}/g);\nlet parsed = {};\n\nif (matches && matches.length) {\n  // pick the deepest block (most nested)\n  let jsonText = matches[matches.length - 1]\n    .replace(/\u201c|\u201d/g, '\"')\n    .replace(/\u2018|\u2019/g, \"'\")\n    .replace(/,\\s*([}\\]])/g, '$1')\n    .replace(/'/g, '\"')\n    .trim();\n\n  try {\n    parsed = JSON.parse(jsonText);\n  } catch (err) {\n    // attempt repair if invalid JSON\n    let repaired = jsonText.replace(/([{,]\\s*)([A-Za-z0-9_]+)\\s*:/g, '$1\"$2\":');\n    try {\n      parsed = JSON.parse(repaired);\n    } catch (err2) {\n      parsed = {};\n    }\n  }\n}\n\n// 3\ufe0f\u20e3 Regex fallback if still not parsed\nif (!parsed.title) {\n  parsed.title = text.match(/\"title\"\\s*:\\s*\"([^\"]+)\"/i)?.[1]\n    || text.match(/title\\s*[:\\-]\\s*(.+)/i)?.[1]?.trim()\n    || '';\n}\nif (!parsed.slug) {\n  parsed.slug = text.match(/\"slug\"\\s*:\\s*\"([^\"]+)\"/i)?.[1]\n    || text.match(/slug\\s*[:\\-]\\s*(.+)/i)?.[1]?.trim()\n    || '';\n}\nif (!parsed.excerpt) {\n  parsed.excerpt = text.match(/\"excerpt\"\\s*:\\s*\"([^\"]+)\"/i)?.[1]\n    || text.match(/excerpt\\s*[:\\-]\\s*(.+)/i)?.[1]?.trim()\n    || '';\n}\nif (!parsed.content) {\n  const contentMatch = text.match(/\"content\"\\s*:\\s*\"([\\s\\S]+)\"/i);\n  parsed.content = contentMatch\n    ? contentMatch[1]\n    : text.match(/content\\s*[:\\-]\\s*([\\s\\S]+)/i)?.[1]?.trim()\n    || '';\n}\nif (!parsed.keywords) {\n  parsed.keywords = text.match(/\"keywords\"\\s*:\\s*\"([^\"]+)\"/i)?.[1]\n    || text.match(/keywords?\\s*[:\\-]\\s*(.+)/i)?.[1]?.trim()\n    || '';\n}\nif (!parsed.image_prompt) {\n  parsed.image_prompt = text.match(/\"image_prompt\"\\s*:\\s*\"([^\"]+)\"/i)?.[1]\n    || text.match(/image[_\\s]?prompt\\s*[:\\-]\\s*(.+)/i)?.[1]?.trim()\n    || '';\n}\n\n// 4\ufe0f\u20e3 Normalize and sanitize output\nparsed.title = parsed.title?.replace(/^[\"']|[\"']$/g, '') || 'Untitled Blog';\nparsed.slug =\n  parsed.slug ||\n  parsed.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');\nparsed.excerpt = parsed.excerpt?.replace(/^[\"']|[\"']$/g, '') || parsed.content.slice(0, 160);\nparsed.content = parsed.content?.replace(/^[\"']|[\"']$/g, '') || '';\nparsed.keywords = parsed.keywords?.replace(/^[\"']|[\"']$/g, '') || '';\nparsed.image_prompt =\n  parsed.image_prompt?.replace(/^[\"']|[\"']$/g, '') || parsed.title;\n\n// 5\ufe0f\u20e3 Output ready-to-use normalized data\nreturn [parsed];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "02a8a518-fb7d-4f13-b72d-25dcbacfb033",
      "name": "Wait",
      "type": "n8n-nodes-base.wait",
      "position": [
        1824,
        -240
      ],
      "parameters": {
        "amount": 20
      },
      "typeVersion": 1.1
    },
    {
      "id": "e2f813a3-1808-44f7-9bae-842d8d6cecbc",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        2048,
        -240
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "31dea76b-5ba5-464c-a85f-cac7facabdfb",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.status }}",
              "rightValue": "=succeeded"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "b300ab84-eeea-42ea-a609-6bff06da4846",
      "name": "Edit Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        2272,
        -240
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "={\n  \"title\": \"{{ $('Code in JavaScript').item.json.title }}\",\n  \"slug\": \"{{ $('Code in JavaScript').item.json.slug }}\",\n  \"excerpt\": \"{{ $('Code in JavaScript').item.json.excerpt }}\",\n  \"content\": \"{{ $('Code in JavaScript').item.json.content }}\",\n  \"image_url\": \"{{ $json.output }}\",\n  \"published\": true\n}"
      },
      "typeVersion": 3.4
    },
    {
      "id": "df01fb3f-d7dd-4d2b-9581-24df4472b3ea",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.cron",
      "position": [
        0,
        -240
      ],
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "hour": 10
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "03a19edc-7128-40d1-a936-7fa3bce230a2",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        -48
      ],
      "parameters": {
        "color": 4,
        "width": 384,
        "height": 512,
        "content": "### \ud83c\udf10 **Fetch Industry Trends (HTTP Request \u2013 NewsAPI)**\n\n**Purpose:**\nFetches the latest 10 articles related to \u201cAI automation SaaS\u201d from NewsAPI.\nThese articles are used as context for generating trending blog topics.\n\n**Credentials Required:**\n\n* `NewsAPI` (Generic Query Auth) \u2014 Use your API key from [https://newsapi.org](https://newsapi.org).\n\n**Notes:**\n\n* You can modify the `q` parameter to match your industry (e.g., \u201cfinance AI,\u201d \u201chealthcare automation,\u201d etc.).\n* You can also adjust `pageSize` to fetch more or fewer results.\n* NewsAPI free tier allows 100 requests/day \u2014 plan accordingly if running multiple times.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "dc40791a-0f73-45dd-8856-30d3ea8b88fe",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        304,
        -672
      ],
      "parameters": {
        "color": 5,
        "width": 352,
        "height": 416,
        "content": "### \ud83e\udd16 **Message a model (Google Gemini)**\n\n**Purpose:**\nAsks Gemini to analyze current news trends and generate **1 trending blog topic** idea in JSON format (title, description, keywords).\n\n**Credentials Required:**\n\n* `Google Gemini (PaLM) API Key`\n\n**Notes:**\n\n* Returns strict JSON to make downstream parsing easier.\n* You can modify the prompt for your own industry context (e.g., \u201cBased on fintech trends\u2026\u201d).\n* Recommended model: `gemini-2.5-flash` for faster responses."
      },
      "typeVersion": 1
    },
    {
      "id": "b2863fad-0e3e-4f6d-a5e9-c083a37c7962",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        624,
        -48
      ],
      "parameters": {
        "color": 5,
        "width": 432,
        "height": 416,
        "content": "### \u270d\ufe0f **Message a model1 (Google Gemini)**\n\n**Purpose:**\nGenerates a full-length blog (1200\u20131500 words) on the selected topic, optimized for SEO and structured in Markdown.\n\n**Credentials Required:**\n\n* Same `Google Gemini (PaLM)` credential as above.\n\n**Notes:**\n\n* The prompt includes explicit instructions for SEO title, meta description, excerpt, and structured JSON output.\n* You can tune word length or format (e.g., shorter LinkedIn post version) by editing the prompt text.\n* Output is JSON, but sometimes Gemini may include Markdown or text noise \u2014 the next node handles cleanup."
      },
      "typeVersion": 1
    },
    {
      "id": "c024e432-6579-4846-bd85-3d1d6cc8c047",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        864,
        -672
      ],
      "parameters": {
        "color": 3,
        "width": 400,
        "height": 416,
        "content": "### \ud83e\uddf9 **Code in JavaScript**\n\n**Purpose:**\nCleans, parses, and repairs any messy AI output to ensure consistent JSON fields (title, slug, excerpt, content, keywords, image_prompt).\n\n**Notes:**\n\n* This code is **resilient** against broken Markdown, mixed quotes, and malformed JSON.\n* It automatically normalizes the slug, removes extra quotation marks, and trims long text.\n* If Gemini output fails to parse, it still produces fallback fields like `Untitled Blog`.\n* No credentials required.\n\n\ud83d\udca1 *You can safely modify this node if you add or remove output fields from the AI prompt.*"
      },
      "typeVersion": 1
    },
    {
      "id": "1792857d-0095-4aaf-93d3-3137a6f7c59d",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1136,
        -16
      ],
      "parameters": {
        "color": 4,
        "width": 384,
        "height": 432,
        "content": "### \ud83d\uddbc\ufe0f **HTTP Request (Replicate \u2013 Generate Image)**\n\n**Purpose:**\nSends the `image_prompt` field to Replicate\u2019s **Ideogram v3 Turbo** model to generate a 1024\u00d71024 image.\n\n**Credentials Required:**\n\n* `Replicate Bearer YOUR_TOKEN_HERE` from [https://replicate.com/account](https://replicate.com/account)\n\n**Notes:**\n\n* Modify `width`/`height` for other image sizes.\n* You can swap model endpoint with any text-to-image API you prefer.\n* Returns a `prediction ID` and temporary URL for polling."
      },
      "typeVersion": 1
    },
    {
      "id": "35e16700-c40d-4e8b-abda-a55b193b7e1f",
      "name": "HTTP Request1 (Replicate Polling)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1600,
        -240
      ],
      "parameters": {
        "url": "={{ $json.urls.get }}",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth"
      },
      "credentials": {
        "httpBearerAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "6c7dfb6f-4f83-4d65-bea0-56c12a7b5393",
      "name": "HTTP Request (Replicate \u2013 Generate Image)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1376,
        -240
      ],
      "parameters": {
        "url": "https://api.replicate.com/v1/models/ideogram-ai/ideogram-v3-turbo/predictions",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"input\": {\n    \"prompt\": \"{{ $json.image_prompt }}\",\n    \"width\": 1024,\n    \"height\": 1024\n  }\n}\n",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth"
      },
      "credentials": {
        "httpBearerAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "9a54a8e9-938b-4e24-8f50-6681b64e53d7",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1744,
        -32
      ],
      "parameters": {
        "width": 256,
        "height": 368,
        "content": "### \u23f1\ufe0f **Wait**\n\n**Purpose:**\nAdds a 20-second delay before rechecking Replicate\u2019s image status.\n\n**Notes:**\n\n* Replicate image generation takes ~10\u201320 seconds usually.\n* You can increase or decrease the wait time depending on your model speed.\n* Prevents rate-limit errors by spacing out API calls.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "e4691aa3-b13b-447a-9a13-86787ecacac6",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1456,
        -656
      ],
      "parameters": {
        "color": 4,
        "width": 352,
        "height": 400,
        "content": "### \u23f3 **HTTP Request1 (Replicate Polling)**\n\n**Purpose:**\nFetches the status of the image generation request from Replicate using the returned `prediction ID`.\n\n**Credentials Required:**\n\n* Same Replicate Bearer YOUR_TOKEN_HERE as above.\n\n**Notes:**\n\n* The polling is used in combination with the **Wait** and **If** nodes.\n* Returns `status` = `succeeded` once image is ready.\n* You can also extract error messages if the job fails."
      },
      "typeVersion": 1
    },
    {
      "id": "a363eaf7-f137-4977-aec6-e714d761f3ff",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1888,
        -592
      ],
      "parameters": {
        "width": 304,
        "height": 336,
        "content": "### \u2705 **If**\n\n**Purpose:**\nChecks if the Replicate image generation has **succeeded**.\n\n**Notes:**\n\n* If the status = \u201csucceeded,\u201d workflow proceeds to the next step.\n* If not, it loops back to **HTTP Request1** to poll again.\n* This ensures the workflow doesn\u2019t break while waiting for long image generations.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "bdec9233-16ac-4054-b953-69e6351b9c50",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2128,
        -80
      ],
      "parameters": {
        "width": 304,
        "height": 352,
        "content": "### \ud83e\udde9 **Edit Fields (Set Node)**\n\n**Purpose:**\nCombines cleaned text and generated image URL into a final JSON payload for publishing.\n\n**Notes:**\n\n* Merges `title`, `slug`, `excerpt`, `content`, and `image_url` fields into one structured object.\n* Adds `\"published\": true` flag for Supabase insert.\n* You can add extra custom fields like `category` or `author` here easily.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "f00d7334-5237-4b3c-ac73-d07164ad5ab7",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2272,
        -720
      ],
      "parameters": {
        "color": 4,
        "width": 432,
        "height": 480,
        "content": "### \ud83d\ude80 **Publish to Supabase (HTTP Request)**\n\n**Purpose:**\nPublishes the final blog post to Supabase via your custom function endpoint.\n\n**Credentials Required:**\n\n* `Supabase Header Auth` (e.g., `Authorization: Bearer YOUR_TOKEN_HERE`)\n\n**Notes:**\n\n* The example endpoint is:\n\n  ```\n  https://your-project.supabase.co/functions/v1/blog-api\n  ```\n* Make sure your Supabase Edge Function expects JSON with the fields provided.\n* You can replace this with your own CMS (WordPress, Ghost, Webflow, etc.) API endpoint."
      },
      "typeVersion": 1
    },
    {
      "id": "5fe82ef4-24c2-46ae-b0de-e6986bcb3668",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2704,
        -80
      ],
      "parameters": {
        "color": 2,
        "width": 336,
        "height": 480,
        "content": "### \ud83d\udcac **Slack Notification**\n\n**Purpose:**\nNotifies your Slack channel with the published blog details.\n\n**Credentials Required:**\n\n* Slack API token (with `chat:write` permission).\n\n**Notes:**\n\n* Message includes blog title, slug, and URL (`https://utsltech.com/blog/{{slug}}`).\n* You can customize channel name and message formatting (supports Block Kit).\n* Great for instant visibility of published content.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "b1208c9f-d79c-405a-8d1c-96d0a5ec7389",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -240,
        -816
      ],
      "parameters": {
        "width": 480,
        "height": 336,
        "content": "## \ud83e\udde9 General Notes for Template\n\n* **All credentials** should be created before importing the workflow.\n* **Error handling:** This workflow includes a fallback for Gemini output; you can add a global error workflow for API failures.\n* **Customization:** You can easily extend the flow to auto-share to LinkedIn, Twitter, or email subscribers.\n* **Runtime:** Average runtime ~1\u20132 minutes depending on image generation time.\n* **Timezone:** Set to `Asia/Kolkata` by default \u2014 adjust if needed in workflow settings.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "13f542b7-566c-429d-ba5d-45a63b7fd801",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1040,
        -816
      ],
      "parameters": {
        "width": 784,
        "height": 1696,
        "content": "The **AI Blog Creator with Gemini, Replicate Image, Supabase Publishing & Slack** is a fully automated content generation and publishing workflow designed for modern marketing and SaaS teams.\nIt automatically fetches the latest industry trends, generates SEO-optimized blogs using AI, creates a relevant featured image, publishes the post to your CMS (e.g., Supabase or custom API), and notifies your team via Slack all on a daily schedule.\n\nThis workflow connects multiple services NewsAPI, Google Gemini, Replicate, Supabase, and Slack into one intelligent content pipeline that runs hands-free once set up.\n\n---\n\n### **\u2728 Features**\n\n* \ud83d\udcf0 **Fetch Trending Topics** \u2014 pulls the latest news or updates from your selected industry (via NewsAPI).\n* \ud83e\udd16 **AI Topic Generation** \u2014 Gemini suggests trending blog topics relevant to AI, SaaS, and Automation.\n* \ud83d\udcdd **AI Blog Authoring** \u2014 Gemini then writes a full 1200-1500 word SEO-optimized article in Markdown.\n* \ud83e\uddf9 **Smart JSON Cleaner** \u2014 A resilient code node parses Gemini\u2019s output and ensures clean, structured data.\n* \ud83d\uddbc\ufe0f **Auto-Generated Image** \u2014 Replicate\u2019s Ideogram model creates a blog cover image based on the content prompt.\n* \ud83c\udf10 **Automatic Publishing** \u2014 Posts are automatically published to your Supabase or custom backend.\n* \ud83d\udcac **Slack Notification** \u2014 Notifies your team with blog details and live URL.\n* \u23f0 **Fully Scheduled** \u2014 Runs automatically every day at your preferred time (default 10 AM IST).\n\n---\n\n### **\u2699\ufe0f Workflow Structure**\n\n| Step | Node                                | Purpose                                         |\n| ---- | ----------------------------------- | ----------------------------------------------- |\n| 1    | **Schedule Trigger**                | Runs daily at 10 AM                             |\n| 2    | **Fetch Industry Trends (NewsAPI)** | Retrieves trending articles                     |\n| 3    | **Message a model (Gemini)**        | Generates trending topic ideas                  |\n| 4    | **Message a model1 (Gemini)**       | Writes full SEO blog content                    |\n| 5    | **Code in JavaScript**              | Cleans, validates, and normalizes Gemini output |\n| 6    | **HTTP Request (Replicate)**        | Generates an image using Ideogram               |\n| 7    | **HTTP Request1**                   | Retrieves generated image URL                   |\n| 8    | **Wait + If**                       | Polls until image generation succeeds           |\n| 9    | **Edit Fields**                     | Assembles blog fields into final JSON           |\n| 10   | **Publish to Supabase**             | Posts to your CMS                               |\n| 11   | **Slack Notification**              | Sends message to your Slack channel             |\n\n---\n\n### **\ud83d\udd27 Setup Instructions**\n\n1. **Import the Workflow** in n8n and enable it.\n2. Create the following credentials:\n\n   * **NewsAPI (Query Auth)** \u2014 from [https://newsapi.org](https://newsapi.org)\n   * **Google Gemini (PaLM API)** \u2014 use your Gemini API key\n   * **Replicate (Bearer YOUR_TOKEN_HERE)** \u2014 API key from [https://replicate.com/account](https://replicate.com/account)\n   * **Supabase (Header Auth)** \u2014 endpoint to your `/functions/v1/blog-api` (set your key in header)\n   * **Slack API** \u2014 create a Slack App token with `chat:write` permission\n3. Edit the **NewsAPI URL** query parameter to match your industry (e.g., `q=AI automation SaaS`).\n4. Update the **Supabase publish URL** to your project endpoint if needed.\n5. Adjust the **Slack Channel** name under \u201cSlack Notification\u201d.\n6. (Optional) Change the **Schedule Trigger** time as per your timezone.\n\n---\n\n### **\ud83d\udca1 Notes & Tips**\n\n* The **Code in JavaScript** node is robust against malformed or extra text in Gemini output \u2014 it sanitizes Markdown and reconstructs clean JSON safely.\n* You can replace Supabase with any CMS or Webhook endpoint by editing the \u201cPublish to Supabase\u201d node.\n* The Replicate model used is `ideogram-ai/ideogram-v3-turbo` \u2014 you can swap it with Stable Diffusion or another model for different aesthetics.\n* Use the `slug` field in your blog URLs for SEO-friendly links.\n* Test with one manual execution before activating scheduled runs.\n* If Slack notification fails, verify the token scopes and channel permissions.\n\n"
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "timezone": "Asia/Kolkata",
    "callerPolicy": "workflowsFromSameOwner",
    "errorWorkflow": "EmPnnzwJEHxMuJaw",
    "executionOrder": "v1",
    "saveExecutionProgress": true,
    "saveDataErrorExecution": "all"
  },
  "versionId": "bc157604-a8a8-4745-a98d-d8b10da1b1ab",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "HTTP Request1 (Replicate Polling)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields": {
      "main": [
        [
          {
            "node": "Publish to Supabase",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Message a model": {
      "main": [
        [
          {
            "node": "Message a model1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Message a model1": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Fetch Industry Trends",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "HTTP Request (Replicate \u2013 Generate Image)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack Notification": {
      "main": [
        []
      ]
    },
    "Publish to Supabase": {
      "main": [
        [
          {
            "node": "Slack Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Industry Trends": {
      "main": [
        [
          {
            "node": "Message a model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request1 (Replicate Polling)": {
      "main": [
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request (Replicate \u2013 Generate Image)": {
      "main": [
        [
          {
            "node": "HTTP Request1 (Replicate Polling)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}