{
  "name": "autopost_Facebook",
  "nodes": [
    {
      "parameters": {
        "content": "# AI Facebook Post Automation\n\n**What this does:**\nGenerates and publishes `YOUR_NICHE`-themed Facebook posts, either on a 12h schedule or on-demand via Telegram message. Every post is researched (via SerpApi news search), checked against recent post history to avoid repetition/duplicates, and requires human approval in Telegram before publishing.\n\n**Flow overview:**\n1. Trigger (Telegram message OR schedule)\n2. Auth check (Telegram only \u2014 ignores messages from anyone but authorized chat IDs)\n3. Normalize input from either trigger into one common shape\n4. Load recent post history (for dedup context)\n5. Generate post (LLM agent + news search tool)\n6. Duplicate check against post history\n7. Send to Telegram for approval\n8. If approved \u2192 publish to Facebook, log to history, notify success\n9. If rejected \u2192 notify rejection\n10. If publish fails \u2192 notify failure separately\n\n**\u26a0\ufe0f Before running, finish set up:**\n- Replace `YOUR_TELEGRAM_CHAT_ID` (your Telegram chat ID) wherever it appears \u2014 see the yellow note near the trigger.\n- Connect all credentials (OpenAI, Telegram, SerpApi, Zernio API).\n",
        "height": 844,
        "width": 460,
        "color": 4
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -496,
        176
      ],
      "id": "bf4fc044-aee2-495a-817e-27040699b2a7",
      "name": "Sticky: Overview"
    },
    {
      "parameters": {
        "content": "## Trigger & Auth\n\n**Schedule** or **Telegram**\n\n**Update `YOUR_TELEGRAM_CHAT_ID` in `If Chat Authorized`** (rightValue) to your own Telegram chat ID. \n\nUnauthorized messages are silently dropped at `Ignore Unauthorized` (NoOp) \u2014 no error, no notification, no generation triggered.",
        "height": 1020,
        "width": 640,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        0
      ],
      "id": "a1d47909-168c-4f9f-9023-cb407d96340e",
      "name": "Sticky: Trigger & Auth"
    },
    {
      "parameters": {
        "content": "## Deduplication\n\n`Prepare Generation Context` reads the last 5 published posts from **workflow static data** (`$getWorkflowStaticData('global')`) \u2014 a lightweight built-in store, no external DB needed. It builds a short summary of recent hooks/openings and injects it into the generation prompt so the LLM actively avoids repeating itself.\n\nAfter generation, `Check Duplicate` does a second, stricter pass: it normalizes and compares the *exact* new post text (and its first ~15 words) against everything in history. If it matches, the Telegram approval message is flagged with a \u26a0\ufe0f warning so you can catch it before publishing.\n\nOnly after a **successful** publish does `Save Post To History` append the new post (capped at last 20 entries).\n\n**Scaling note:** workflow static data lives inside this workflow's JSON and is fine for single-instance, moderate-volume use. But ideally you need Airtable/Postgres/Google Sheets instead.",
        "height": 900,
        "width": 652,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        656,
        112
      ],
      "id": "411b2d4d-af11-493d-b84f-a2c49efe0ac3",
      "name": "Sticky: Deduplication"
    },
    {
      "parameters": {
        "content": "## Approval\n\nA Telegram message is sent for approval (Send and wait) with timeout  (`limitWaitTime`) so the execution doesn't sit open forever if you miss the Telegram prompt.\n\nThe approval message shows a \u26a0\ufe0f duplicate warning when `Check Duplicate` flags a repeat, so you can reject or regenerate instead of publishing a near-identical post.",
        "height": 788,
        "width": 540,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1328,
        224
      ],
      "id": "fa156601-8562-4386-9530-6fd62631d475",
      "name": "Sticky: Approval"
    },
    {
      "parameters": {
        "content": "##  Publish & Error Handling\n\n`Create post` has `onError: continueErrorOutput` set, giving it two outputs:\n- **Success** \u2192 `Save Post To History` \u2192 `Notify Published`\n- **Error** \u2192 `Notify Publish Failed` (separate Telegram alert, includes the post text so you can publish it manually if needed)\n\n",
        "height": 808,
        "width": 784,
        "color": 2
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1888,
        208
      ],
      "id": "f523c61c-4dbc-4ecc-acad-2b7b64b409fb",
      "name": "Sticky: Publish & Errors"
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-4.1",
          "mode": "list",
          "cachedResultName": "gpt-4.1"
        },
        "builtInTools": {},
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.3,
      "position": [
        976,
        864
      ],
      "id": "c95006af-1abd-422e-ab43-a2535b69bac5",
      "name": "OpenAI Chat Model",
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "YOUR_TELEGRAM_CHAT_ID",
        "text": "\u2705 Your Facebook post has been published!",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "HTML"
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2496,
        512
      ],
      "id": "e23fcaa9-69d0-44d1-8ee2-f245e5dafd90",
      "name": "Notify Published",
      "retryOnFail": true,
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "411c8e56-256d-4205-a5e1-2571c6071a41",
      "name": "Search Trending News",
      "type": "@n8n/n8n-nodes-langchain.toolSerpApi",
      "typeVersion": 1,
      "position": [
        1120,
        864
      ],
      "credentials": {
        "serpApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "sendAndWait",
        "chatId": "YOUR_TELEGRAM_CHAT_ID",
        "message": "=New Facebook post ready for review:\n\n{{ $json.isDuplicate ? '\u26a0\ufe0f POSSIBLE DUPLICATE \u2014 similar to a post from ' + $json.matchedDate + '\\n\\n' : '' }}{{ $json.output }}\n\nApprove to publish?",
        "approvalOptions": {
          "values": {
            "approvalType": "double",
            "approveLabel": "Approve",
            "disapproveLabel": "Reject"
          }
        },
        "options": {
          "limitWaitTime": {
            "values": {
              "resumeAmount": 24
            }
          },
          "appendAttribution": false
        }
      },
      "id": "6b4b3a2a-5570-4f48-8cd1-49c39fea484e",
      "name": "Request Approval",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1568,
        672
      ],
      "retryOnFail": true,
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "cond-approved-1",
              "leftValue": "={{ $json.data.approved }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "0c20a541-23fc-4316-b08e-f6950dcbb926",
      "name": "If Approved",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1760,
        672
      ]
    },
    {
      "parameters": {
        "chatId": "YOUR_TELEGRAM_CHAT_ID",
        "text": "\u274c Post was rejected and was NOT published to Facebook.",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "HTML"
        }
      },
      "id": "5152ed59-08d2-481d-91e3-0c4920efedd9",
      "name": "Notify Rejected",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2000,
        832
      ],
      "retryOnFail": true,
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.3,
      "position": [
        64,
        512
      ],
      "id": "5513c88e-9677-44ba-9110-c9cd7e8b6ea1",
      "name": "Telegram Trigger",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "cond-auth-1",
              "leftValue": "={{ $json.message.chat.id }}",
              "rightValue": "YOUR_TELEGRAM_CHAT_ID",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "87c983ee-11c9-4104-935b-b735879551ab",
      "name": "If Chat Authorized",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        256,
        512
      ]
    },
    {
      "parameters": {},
      "id": "6602dfdc-2fb4-49ca-a3f1-3de6e89199f5",
      "name": "Ignore Unauthorized",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        464,
        320
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "assign-usermsg-1",
              "name": "userMessage",
              "value": "={{ $json.message.text }}",
              "type": "string"
            },
            {
              "id": "assign-source-1",
              "name": "source",
              "value": "telegram",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        464,
        496
      ],
      "id": "b534387d-849c-40af-8a6d-3cd36dcdb9ec",
      "name": "Normalize Telegram Input"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 12
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [
        64,
        832
      ],
      "id": "ebe271bc-ecd1-4efa-aff2-d1e687f10dd2",
      "name": "Every 12 hours"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "assign-usermsg-2",
              "name": "userMessage",
              "value": "",
              "type": "string"
            },
            {
              "id": "assign-source-2",
              "name": "source",
              "value": "schedule",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        464,
        832
      ],
      "id": "33e8e119-e022-451d-8293-f193722bf66d",
      "name": "Normalize Schedule Input"
    },
    {
      "parameters": {
        "jsCode": "const staticData = $getWorkflowStaticData('global');\nif (!staticData.recentPosts) {\n  staticData.recentPosts = [];\n}\n\nconst recent = staticData.recentPosts.slice(-5);\nlet recentPostsContext = 'No previous posts recorded yet \u2014 write freely.';\n\nif (recent.length > 0) {\n  recentPostsContext = recent.map((p, i) => {\n    const firstLine = (p.text || '').split('\\n').filter(l => l.trim()).slice(0, 2).join(' ');\n    return `${i + 1}. (${p.date}) Opening: \"${firstLine.substring(0, 120)}\"`;\n  }).join('\\n');\n}\n\nconst userMessage = $json.userMessage || '';\nconst source = $json.source || 'unknown';\n\nreturn [{\n  json: {\n    userMessage,\n    source,\n    recentPostsContext\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        672,
        672
      ],
      "id": "40c55798-8a82-41b1-b3fb-3d50939e9602",
      "name": "Prepare Generation Context"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Write a Facebook post about a topic in AI automation and Business solutions with AI automaton. \nUse user message as guidance if given. \n\nuser message: {{ $('Prepare Generation Context').item.json.userMessage }}\n\nBefore writing, use the search tool to find one current, relevant news item, statistic, or trend related to the topic \u2014 use it to make the post feel timely and informed.\n\n## AVOID REPEATING RECENT POSTS\nHere are summaries of your most recent posts (openings/hooks). Vary your hook, structure, and CTA so this post feels distinct from these:\n{{ $('Prepare Generation Context').item.json.recentPostsContext }}\n\nRequirements:\n- Use line breaks for readability\n- Include the reference for the study or statistic you're referencing in the end of the post and the actual link to that refernce so people can manually check it. \n- Do NOT include any preamble like \"Here is your post:\" \u2014 return ONLY the post text",
        "options": {
          "systemMessage": "You are an expert social media copywriter and AI automation consultant who writes high-performing Facebook posts for a business that sells AI automation solutions (chatbots, workflow automation, AI agents, process optimization, etc.) to other businesses.\n\n## YOUR ROLE\nGenerate a single, ready-to-publish Facebook post each time you're triggered. Your audience is business owners, operations managers, and entrepreneurs who are curious about AI but may not be technical. Your goal is to educate, build authority, and generate interest in AI automation services \u2014 NOT to hard-sell in every post.\n\n## TOPIC FOCUS\nDraw from (and vary between) these themes:\n- Practical use cases of AI automation in business (customer service, lead gen, scheduling, data entry, reporting, etc.)\n- Time/cost savings and ROI of automating repetitive tasks\n- Common misconceptions about AI (\"AI will replace all jobs\" vs. reality)\n- Behind-the-scenes look at how AI agents/workflows work (in simple terms)\n- Industry trends in AI and business automation\n- Quick tips business owners can apply themselves\n- Case-study style posts (hypothetical or general, no fabricated client names/stats)\n\nUse the \"Search Trending News\" tool when available to reference current, relevant AI/automation news or trends \u2014 this keeps content fresh and timely. Never fabricate statistics, sources, or news events.\n\n## TONE & STYLE\n- Conversational, confident, and approachable \u2014 not overly corporate or jargon-heavy\n- Explain technical concepts in plain language a non-technical business owner would understand\n- Use short paragraphs and line breaks for readability (Facebook, not LinkedIn \u2014 keep it punchy)\n- Light use of emojis is okay (1\u20134 per post) to add visual breaks, not excessive\n- No clickbait, no fear-mongering about AI \"taking over\"\n\n## STRUCTURE\n1. **Hook** \u2014 first 1\u20132 lines must grab attention (a question, bold statement, or relatable pain point)\n2. **Body** \u2014 2\u20134 short paragraphs delivering value: an insight, tip, or example\n3. **CTA** \u2014 end with a soft call-to-action (e.g., \"Drop a comment if you've tried this,\" \"DM us to see how this could work for your business,\" \"Follow for more AI tips\")\n4. **Hashtags** \u2014 3\u20135 relevant hashtags at the end (e.g., #AIAutomation #BusinessAutomation #SmallBusinessAI #WorkflowAutomation #AIforBusiness)\n\n## LENGTH\nKeep the post between 80\u2013180 words (excluding hashtags). Facebook rewards concise, scannable content over long-form text.\n\n## OUTPUT FORMAT\nOutput ONLY the final post text, ready to publish. Do not include explanations, options, headers, or meta-commentary like \"Here's your post.\" Do not wrap the output in quotes or code blocks.\n\n## CONSTRAINTS\n- Never mention specific client names, fake testimonials, or invented statistics/sources\n- Never make promises of guaranteed results (e.g., \"guaranteed to save you $10k\")\n- Never use overly salesy language (\"ACT NOW,\" \"LIMITED TIME,\" excessive exclamation marks)\n- Avoid repeating the same hook, structure, or CTA style used in recent posts \u2014 vary it each time"
        }
      },
      "id": "f579e3dd-72b9-406e-8c21-1c14cb0954e2",
      "name": "Generate Facebook Post",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [
        976,
        672
      ],
      "retryOnFail": true
    },
    {
      "parameters": {
        "jsCode": "const staticData = $getWorkflowStaticData('global');\nconst recentPosts = staticData.recentPosts || [];\n\nconst newPost = $json.output || '';\n\nfunction normalize(text) {\n  return text.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();\n}\n\nconst normalizedNew = normalize(newPost);\n\nlet isDuplicate = false;\nlet matchedDate = null;\n\nfor (const post of recentPosts) {\n  const normalizedOld = normalize(post.text || '');\n\n  // exact match\n  if (normalizedOld === normalizedNew) {\n    isDuplicate = true;\n    matchedDate = post.date;\n    break;\n  }\n\n  // near-duplicate: same opening ~15 words (same hook reused)\n  const newOpening = normalizedNew.split(' ').slice(0, 15).join(' ');\n  const oldOpening = normalizedOld.split(' ').slice(0, 15).join(' ');\n  if (newOpening.length > 20 && newOpening === oldOpening) {\n    isDuplicate = true;\n    matchedDate = post.date;\n    break;\n  }\n}\n\nreturn [{\n  json: {\n    output: newPost,\n    isDuplicate,\n    matchedDate\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1344,
        672
      ],
      "id": "9a1515f7-004a-4e41-9659-16e047b88b94",
      "name": "Check Duplicate"
    },
    {
      "parameters": {
        "resource": "posts",
        "operation": "create",
        "content": "={{ $('Check Duplicate').item.json.output }}",
        "publishNow": true,
        "selectedPlatforms": [
          "facebook"
        ],
        "facebookAccounts": [
          "YOUR_FACEBOOK_ACCOUNT_ID"
        ],
        "mediaItems": {
          "items": []
        },
        "requestOptions": {}
      },
      "type": "n8n-nodes-late.late",
      "typeVersion": 1,
      "position": [
        1984,
        656
      ],
      "id": "fdccf433-acbf-40b2-be34-0907c7fc19cc",
      "name": "Create post",
      "credentials": {
        "lateApi": {
          "name": "<your credential>"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "jsCode": "const staticData = $getWorkflowStaticData('global');\nif (!staticData.recentPosts) {\n  staticData.recentPosts = [];\n}\n\nconst postText = $('Check Duplicate').item.json.output;\n\nstaticData.recentPosts.push({\n  text: postText,\n  date: new Date().toISOString()\n});\n\n// keep only the last 20 posts to avoid unbounded growth\nif (staticData.recentPosts.length > 20) {\n  staticData.recentPosts = staticData.recentPosts.slice(-20);\n}\n\nreturn [{\n  json: {\n    saved: true,\n    totalStored: staticData.recentPosts.length\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2256,
        512
      ],
      "id": "a0fbcc70-76bc-4915-94f6-9fd5022bdcac",
      "name": "Save Post To History"
    },
    {
      "parameters": {
        "chatId": "YOUR_TELEGRAM_CHAT_ID",
        "text": "=\u26a0\ufe0f Failed to publish to Facebook. Please check the Late API connection and publish manually if needed.\n\nPost content:\n{{ $('Check Duplicate').item.json.output }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "HTML"
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2256,
        832
      ],
      "id": "0bcdd256-ec0f-4cba-9848-9d34f2656d78",
      "name": "Notify Publish Failed",
      "retryOnFail": true,
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "content": "![banner](https://www.kawtheron.tech/github/autopost_facebook_banner.jpg)\n",
        "height": 704,
        "width": 1664,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -720
      ],
      "id": "b09686a5-13f1-4da8-80fb-a1ea4c1201d1",
      "name": "Sticky Note"
    }
  ],
  "connections": {
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Facebook Post",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Search Trending News": {
      "ai_tool": [
        [
          {
            "node": "Generate Facebook Post",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Request Approval": {
      "main": [
        [
          {
            "node": "If Approved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Approved": {
      "main": [
        [
          {
            "node": "Create post",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notify Rejected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Trigger": {
      "main": [
        [
          {
            "node": "If Chat Authorized",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Chat Authorized": {
      "main": [
        [
          {
            "node": "Normalize Telegram Input",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Ignore Unauthorized",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Telegram Input": {
      "main": [
        [
          {
            "node": "Prepare Generation Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every 12 hours": {
      "main": [
        [
          {
            "node": "Normalize Schedule Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Schedule Input": {
      "main": [
        [
          {
            "node": "Prepare Generation Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Generation Context": {
      "main": [
        [
          {
            "node": "Generate Facebook Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Facebook Post": {
      "main": [
        [
          {
            "node": "Check Duplicate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Duplicate": {
      "main": [
        [
          {
            "node": "Request Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create post": {
      "main": [
        [
          {
            "node": "Save Post To History",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notify Publish Failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Post To History": {
      "main": [
        [
          {
            "node": "Notify Published",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "versionId": "35c1900d-0530-4670-ba1d-6429902fb6c8",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodeGroups": [],
  "id": "p4ctXPGCHFnu7xwI",
  "tags": []
}