{
  "name": "Create LinkedIn, Facebook and Bluesky posts with an OpenAI agent, Google Docs and PostWire",
  "nodes": [
    {
      "id": "25d81789-20ff-43a6-a63b-15625789cf6a",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -643,
        -97
      ],
      "parameters": {
        "width": 520,
        "height": 833,
        "content": "## An AI content factory that follows your brand rules\nSend a topic to your Telegram bot and get a post for LinkedIn, Facebook, Bluesky and Mastodon. An OpenAI agent writes them from the brand rules you keep in a Google Doc, and PostWire publishes them after you approve.\n\n### How it works\n1. **New topic in Telegram** takes any text message sent to your bot (commands are skipped).\n2. **Get the brand rules** reads your Google Doc: voice, audience, words to avoid, examples.\n3. **Write the posts with an AI agent** follows those rules, searches Google with SerpApi when it needs fresh facts, and returns one post per network.\n4. **Send the drafts** shows them in your Telegram chat and **Ask for approval in Telegram** waits for Approve or Decline.\n5. On Approve, **Publish with PostWire** posts each network with its own text. Google Sheets logs the result, and the bot replies with the links or the exact fix for each network that refused.\n\n### Setup\n1. Create a free account at postwire.io, connect your networks and copy your API key. In n8n add a **Header Auth** credential (name `Authorization`, value `Bearer pw_live_…`) and select it in **Publish with PostWire**.\n2. Write your brand rules in a Google Doc and paste its URL in **Get the brand rules**.\n3. Add an OpenAI credential to the chat model and a SerpApi key to the search tool (the free plan is enough).\n4. Create a bot with @BotFather and select its credential in the trigger and the five Telegram nodes.\n5. Create a sheet with the columns date, topic, status, links and note, and pick it in both Sheets nodes.\n\n### Customize\nEdit the networks in Read the posts, or swap the chat model."
      },
      "typeVersion": 1
    },
    {
      "id": "5e0b29b9-d5da-4b7b-a41b-b4f555d36119",
      "name": "Sticky Note 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -63,
        -97
      ],
      "parameters": {
        "color": 7,
        "width": 510,
        "height": 271,
        "content": "## 1. Topic from Telegram\nA text message to your bot is the topic."
      },
      "typeVersion": 1
    },
    {
      "id": "fae42d7f-1955-4f48-a189-941d08054b58",
      "name": "Sticky Note 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        544,
        -97
      ],
      "parameters": {
        "color": 7,
        "width": 977,
        "height": 595,
        "content": "## 2. Brand rules and AI agent\nThe Google Doc is the brief; the agent searches when it needs facts and writes one post per network."
      },
      "typeVersion": 1
    },
    {
      "id": "dad94ecd-02fa-43c0-afe9-e75beb972b87",
      "name": "Sticky Note 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -18,
        575
      ],
      "parameters": {
        "color": 7,
        "width": 759,
        "height": 271,
        "content": "## 3. Review in Telegram\nThe drafts arrive in your chat; the workflow waits for Approve or Decline."
      },
      "typeVersion": 1
    },
    {
      "id": "1fa9a444-df2d-477d-a4e5-b479a26ce7db",
      "name": "Sticky Note 5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        798,
        575
      ],
      "parameters": {
        "color": 7,
        "width": 1162,
        "height": 511,
        "content": "## 4. Publish, log and report\nApproved posts go to every network; the sheet gets the result and the bot replies with the links or the fix."
      },
      "typeVersion": 1
    },
    {
      "id": "c1884e90-daf3-4fa8-a3c3-e6d49bbbb580",
      "name": "New topic in Telegram",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        0,
        0
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "78077fda-c3dc-413a-aa2d-c9596e990db2",
      "name": "Only text topics",
      "type": "n8n-nodes-base.filter",
      "position": [
        300,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1449d960-3a87-432e-a783-2e6ca78e63a7",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ String(($json.message || {}).text || '').trim() }}",
              "rightValue": ""
            },
            {
              "id": "105f54fa-3a59-4a5a-af07-664a13e23565",
              "operator": {
                "type": "string",
                "operation": "notStartsWith"
              },
              "leftValue": "={{ String(($json.message || {}).text || '').trim() }}",
              "rightValue": "/"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "32ef671c-fd4c-4edd-a4b5-97be64176824",
      "name": "Get the brand rules",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        600,
        0
      ],
      "parameters": {
        "simple": true,
        "operation": "get",
        "documentURL": "https://docs.google.com/document/d/YOUR_DOCUMENT_ID/edit"
      },
      "typeVersion": 2
    },
    {
      "id": "d7064b7f-7941-448e-a6fa-54eabc517b86",
      "name": "Write the posts with an AI agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        900,
        0
      ],
      "parameters": {
        "text": "={{ 'Topic: ' + $('Only text topics').item.json.message.text.trim() + '\\n\\nBrand rules:\\n' + ($json.content || 'No brand rules yet: write in a clear, friendly, professional voice.') }}",
        "options": {
          "maxIterations": 6,
          "systemMessage": "You are the social media writer of a brand. Follow the brand rules in the message exactly: voice, audience, words to avoid.\nWhen the topic needs current facts, search Google once or twice and use only what you find. Never invent facts, numbers, quotes or links.\nAnswer with one JSON object and nothing else, with these keys:\n- linkedin: 3 short paragraphs, professional, no hashtags.\n- facebook: 1 or 2 conversational sentences.\n- bluesky: up to 280 characters, no hashtags.\n- mastodon: up to 450 characters with 2 hashtags.\n- sources: an array with the URLs you used (empty when you did not search)."
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "79dcaf7f-3edb-4847-a0a5-f05e11b3c777",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        840,
        324
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5-mini",
          "cachedResultName": "gpt-5-mini"
        },
        "options": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "169461bb-5da5-4b40-ac40-438c86954257",
      "name": "Search Google with SerpApi",
      "type": "@n8n/n8n-nodes-langchain.toolSerpApi",
      "position": [
        1110,
        324
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "a3c0da83-0a07-494d-a7dd-53990f5ad3ee",
      "name": "Read the posts",
      "type": "n8n-nodes-base.set",
      "position": [
        1380,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "661f2067-73d1-4016-ae9d-8eb2f5bbb71a",
              "name": "posts",
              "type": "object",
              "value": "={{ (() => { const m = String($json.output || '').match(/\\{[\\s\\S]*\\}/); try { return m ? JSON.parse(m[0]) : {}; } catch (e) { return {}; } })() }}"
            },
            {
              "id": "0b4efba4-3f7d-4776-a8e3-506fba3221da",
              "name": "topic",
              "type": "string",
              "value": "={{ $('Only text topics').item.json.message.text.trim() }}"
            },
            {
              "id": "779974f9-9459-4d10-a61c-057a68157e7d",
              "name": "networks",
              "type": "array",
              "value": "={{ ['linkedin', 'facebook', 'bluesky', 'mastodon'] }}"
            },
            {
              "id": "c5504453-8a90-47ea-aa54-14d538366362",
              "name": "chat_id",
              "type": "string",
              "value": "={{ String($('Only text topics').item.json.message.chat.id) }}"
            },
            {
              "id": "2dfe1c9d-c83e-4cf8-abde-63715e89dcd0",
              "name": "message_id",
              "type": "string",
              "value": "={{ String($('Only text topics').item.json.message.message_id) }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "2245c844-fa13-4a68-acaf-7f7508132bb3",
      "name": "Send the drafts",
      "type": "n8n-nodes-base.telegram",
      "position": [
        0,
        672
      ],
      "parameters": {
        "text": "={{ (() => { const p = $json; const drafts = p.networks.filter(n => p.posts[n]).map(n => '<b>' + n.toUpperCase() + '</b>\\n' + String(p.posts[n]).replace(/&/g, '&amp;').replace(/</g, '&lt;')).join('\\n\\n'); return (drafts || 'The agent returned no posts. Decline and send the topic again.').slice(0, 3900); })() }}",
        "chatId": "={{ $json.chat_id }}",
        "resource": "message",
        "operation": "sendMessage",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f62ce0cb-4124-493c-a0d3-c7205d015712",
      "name": "Ask for approval in Telegram",
      "type": "n8n-nodes-base.telegram",
      "position": [
        300,
        672
      ],
      "parameters": {
        "chatId": "={{ $('Read the posts').item.json.chat_id }}",
        "message": "Publish these posts? Approve sends each one to its network. Decline discards them.",
        "options": {
          "appendAttribution": false
        },
        "resource": "message",
        "operation": "sendAndWait",
        "responseType": "approval",
        "approvalOptions": {
          "values": {
            "approvalType": "double",
            "approveLabel": "Approve",
            "disapproveLabel": "Decline"
          }
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "a61ce02f-8f8a-4e7e-a4bb-1fc58a2605b5",
      "name": "Approved?",
      "type": "n8n-nodes-base.if",
      "position": [
        600,
        672
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "860918f6-9847-458a-a92c-50e51667bce7",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.data.approved }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "76f76dc8-306f-4e9e-a9a7-f09fd28d8135",
      "name": "Publish with PostWire",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        900,
        672
      ],
      "parameters": {
        "url": "https://postwire.io/api/post",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "jsonBody": "={{ (() => { const p = $('Read the posts').item.json; const nets = p.networks.filter(n => p.posts[n]); return JSON.stringify({ platforms: nets, text: p.topic, per_platform: Object.fromEntries(nets.map(n => [n, { text: p.posts[n] }])), idempotency_key: 'factory-' + p.chat_id + '-' + p.message_id }); })() }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "5d450033-9c94-4621-a965-c8c3e6d33512",
      "name": "Every network published?",
      "type": "n8n-nodes-base.if",
      "position": [
        1200,
        672
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "ecc33572-ed31-4adf-ac6c-d85815194b63",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ ($json.results || []).length > 0 && $json.results.every(r => r.ok) }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "25c9ddbe-9441-4a0f-aee5-b331bcb3a127",
      "name": "Log the posts",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1500,
        672
      ],
      "parameters": {
        "columns": {
          "value": {
            "date": "={{ $now.toFormat('yyyy-MM-dd HH:mm') }}",
            "note": "={{ ($('Read the posts').item.json.posts.sources || []).join(' ') }}",
            "links": "={{ ($json.results || []).filter(r => r.ok).map(r => r.platform + ' ' + (r.url || '')).join(' | ') }}",
            "topic": "={{ $('Read the posts').item.json.topic }}",
            "status": "posted"
          },
          "schema": [
            {
              "id": "date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "topic",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "topic",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "links",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "links",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "note",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "note",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "afb6f9e6-0f5c-4443-a498-c54d43ef62a8",
      "name": "Log the failure",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1500,
        912
      ],
      "parameters": {
        "columns": {
          "value": {
            "date": "={{ $now.toFormat('yyyy-MM-dd HH:mm') }}",
            "note": "={{ $json.error ? $json.error : ($json.results || []).filter(r => !r.ok).map(r => r.platform + ': ' + (r.fix || r.error)).join(' | ') }}",
            "links": "={{ ($json.results || []).filter(r => r.ok).map(r => r.platform + ' ' + (r.url || '')).join(' | ') }}",
            "topic": "={{ $('Read the posts').item.json.topic }}",
            "status": "={{ ($json.posted || 0) > 0 ? 'partly posted' : 'needs fix' }}"
          },
          "schema": [
            {
              "id": "date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "topic",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "topic",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "links",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "links",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "note",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "note",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "827f2cb2-af06-4b36-af88-6467b67ed392",
      "name": "Reply with the links",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1800,
        672
      ],
      "parameters": {
        "text": "={{ 'Published everywhere:\\n' + String(($('Publish with PostWire').item.json.results || []).map(r => r.platform + '  ' + (r.url || 'processing, the link appears in PostWire')).join('\\n')).replace(/&/g, '&amp;').replace(/</g, '&lt;') }}",
        "chatId": "={{ $('Read the posts').item.json.chat_id }}",
        "resource": "message",
        "operation": "sendMessage",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "80dbce03-52b2-4296-a8f9-6870c44816f3",
      "name": "Reply with the fix",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1800,
        912
      ],
      "parameters": {
        "text": "={{ 'Not published everywhere:\\n' + String((() => { const r = $('Publish with PostWire').item.json; return r.error ? r.error : (r.results || []).map(x => (x.ok ? 'Published  ' : 'NOT published  ') + x.platform + (x.url ? '  ' + x.url : '') + (x.ok ? '' : '\\n    ' + (x.fix || x.error))).join('\\n'); })()).replace(/&/g, '&amp;').replace(/</g, '&lt;') }}",
        "chatId": "={{ $('Read the posts').item.json.chat_id }}",
        "resource": "message",
        "operation": "sendMessage",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "cdedca4a-c629-482f-a017-9894f399a0a5",
      "name": "Reply that nothing was published",
      "type": "n8n-nodes-base.telegram",
      "position": [
        900,
        912
      ],
      "parameters": {
        "text": "Discarded. Nothing was published. Send the topic again for a new set of drafts.",
        "chatId": "={{ $('Read the posts').item.json.chat_id }}",
        "resource": "message",
        "operation": "sendMessage",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      },
      "typeVersion": 1.2
    }
  ],
  "connections": {
    "Approved?": {
      "main": [
        [
          {
            "node": "Publish with PostWire",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Reply that nothing was published",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log the posts": {
      "main": [
        [
          {
            "node": "Reply with the links",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read the posts": {
      "main": [
        [
          {
            "node": "Send the drafts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log the failure": {
      "main": [
        [
          {
            "node": "Reply with the fix",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send the drafts": {
      "main": [
        [
          {
            "node": "Ask for approval in Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Only text topics": {
      "main": [
        [
          {
            "node": "Get the brand rules",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Write the posts with an AI agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Get the brand rules": {
      "main": [
        [
          {
            "node": "Write the posts with an AI agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "New topic in Telegram": {
      "main": [
        [
          {
            "node": "Only text topics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Publish with PostWire": {
      "main": [
        [
          {
            "node": "Every network published?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every network published?": {
      "main": [
        [
          {
            "node": "Log the posts",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log the failure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Google with SerpApi": {
      "ai_tool": [
        [
          {
            "node": "Write the posts with an AI agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Ask for approval in Telegram": {
      "main": [
        [
          {
            "node": "Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write the posts with an AI agent": {
      "main": [
        [
          {
            "node": "Read the posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "active": false
}
