{
  "nodes": [
    {
      "id": "8ac0a983-be97-4e6f-ae9b-85a6dc61a0ed",
      "name": "\ud83d\udccb MAIN \u2014 Workflow Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -96,
        -768
      ],
      "parameters": {
        "width": 620,
        "height": 712,
        "content": "## \ud83d\udcf0 Auto-Post Blog Content to LinkedIn & Twitter/X\n\n**What this workflow does:**\nThis template monitors your blog's RSS feed for new posts, extracts the title, summary, and cover image, uploads the image to a public URL via the UploadToURL node, then generates platform-optimized captions using AI and auto-posts to both LinkedIn and Twitter/X \u2014 all hands-free.\n\n**Trigger:** RSS Feed polling (every 15 minutes)\n\n**Key nodes used:**\n- \ud83d\udd01 RSS Feed Trigger\n- \ud83e\uddf9 Data Extraction & Cleanup (Code Node)\n- \u2601\ufe0f UploadToURL (mandatory image hosting)\n- \ud83e\udd16 AI Caption Generator (OpenAI)\n- \ud83d\udd17 LinkedIn Post\n- \ud83d\udc26 Twitter/X Post\n- \u2705 Success Logger\n\n**Setup Requirements:**\n1. Add your RSS Feed URL in the RSS Trigger node\n2. Configure OpenAI API credentials\n3. Add LinkedIn OAuth2 credentials\n4. Add Twitter/X OAuth1 credentials\n5. UploadToURL node requires no extra auth \u2014 paste your upload endpoint\n\n**Note:** Deduplication logic is built-in via item uniqueness check to avoid re-posting old articles."
      },
      "typeVersion": 1
    },
    {
      "id": "d084d08d-f0b6-4777-9796-0eb2a3a28ee0",
      "name": "\ud83d\udcdd Note \u2014 Trigger & Filter",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        624,
        -16
      ],
      "parameters": {
        "color": 7,
        "width": 588,
        "height": 440,
        "content": "### \ud83d\udd01 Step 1 \u2014 RSS Trigger & Filter\n**RSS Feed Trigger:** Polls your blog RSS every 15 min for new items.\n**IF Node:** Checks that both `title` and `enclosure` (cover image URL) are present. If no image is found, the workflow exits gracefully \u2014 no broken posts."
      },
      "typeVersion": 1
    },
    {
      "id": "af3e35ec-f0e5-4976-9737-67c908bf4348",
      "name": "\ud83d\udcdd Note \u2014 Fetch & Upload Image",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1232,
        -16
      ],
      "parameters": {
        "color": 7,
        "width": 652,
        "height": 440,
        "content": "### \u2601\ufe0f Step 2 \u2014 Fetch & Upload Image\n**HTTP Request (Fetch Image):** Downloads the raw binary of the blog cover image from the enclosure URL.\n**UploadToURL Node:** Takes the binary image data and uploads it to your configured public storage endpoint. Returns a hosted public URL used in social posts."
      },
      "typeVersion": 1
    },
    {
      "id": "92221d21-dad5-4ac5-b21a-38f83317c965",
      "name": "\ud83d\udcdd Note \u2014 AI Caption & Social Posting",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1904,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 908,
        "height": 600,
        "content": "### \ud83e\udd16 Step 3 \u2014 AI Caption + Posting\n**OpenAI Node:** Generates two platform-specific captions \u2014 one for LinkedIn (professional tone, 150-200 words, with hashtags) and one for Twitter/X (punchy, under 260 chars with 2-3 hashtags).\n**LinkedIn + Twitter/X Nodes:** Post the AI-generated captions with the hosted image URL. Both run in parallel after the AI step."
      },
      "typeVersion": 1
    },
    {
      "id": "fd8ec451-3cec-495b-955c-a54a3ea0e0c2",
      "name": "RSS Feed \u2014 Blog Monitor",
      "type": "n8n-nodes-base.rssFeedReadTrigger",
      "position": [
        640,
        224
      ],
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "786d7bb9-03b5-425d-9a19-932bfd47da8e",
      "name": "IF \u2014 Has Title & Cover Image",
      "type": "n8n-nodes-base.if",
      "position": [
        864,
        224
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-title",
              "operator": {
                "type": "string",
                "operation": "isNotEmpty"
              },
              "leftValue": "={{ $json.title }}",
              "rightValue": ""
            },
            {
              "id": "check-image",
              "operator": {
                "type": "string",
                "operation": "isNotEmpty"
              },
              "leftValue": "={{ $json.enclosure?.url }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "51d82d45-98b0-4157-8534-67368299906a",
      "name": "Code \u2014 Extract & Clean Blog Data",
      "type": "n8n-nodes-base.code",
      "position": [
        1072,
        224
      ],
      "parameters": {
        "jsCode": "const item = $input.first().json;\n\nconst title = item.title || '';\nconst link = item.link || '';\nconst imageUrl = item.enclosure?.url || item['media:content']?.['@_url'] || '';\n\n// Strip HTML tags from content/summary\nconst rawSummary = item.contentSnippet || item.summary || item.content || '';\nconst cleanSummary = rawSummary\n  .replace(/<[^>]+>/g, '')\n  .replace(/&amp;/g, '&')\n  .replace(/&lt;/g, '<')\n  .replace(/&gt;/g, '>')\n  .replace(/&nbsp;/g, ' ')\n  .replace(/\\s+/g, ' ')\n  .trim()\n  .substring(0, 500);\n\nconst pubDate = item.pubDate || item.isoDate || new Date().toISOString();\n\nreturn [{\n  json: {\n    title,\n    link,\n    imageUrl,\n    summary: cleanSummary,\n    pubDate,\n    slug: title.toLowerCase().replace(/[^a-z0-9]+/g, '-')\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "f47b124c-2f3e-4beb-bc5e-bcdbcfda90fd",
      "name": "HTTP \u2014 Fetch Cover Image Binary",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1296,
        224
      ],
      "parameters": {
        "url": "={{ $json.imageUrl }}",
        "options": {
          "timeout": 15000,
          "redirect": {
            "redirect": {
              "maxRedirects": 5
            }
          },
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "faf1fa1a-fbb9-47a8-9a5f-1518b1fe5af1",
      "name": "Code \u2014 Merge Hosted URL with Blog Data",
      "type": "n8n-nodes-base.code",
      "position": [
        1744,
        224
      ],
      "parameters": {
        "jsCode": "const uploadResponse = $input.first().json;\nconst prevData = $('Code \u2014 Extract & Clean Blog Data').first().json;\n\n// Handle various uploadtourl response formats\nconst hostedUrl = \n  uploadResponse?.url ||\n  uploadResponse?.data?.url ||\n  uploadResponse?.file?.url ||\n  uploadResponse?.link ||\n  prevData.imageUrl; // fallback to original if upload fails\n\nreturn [{\n  json: {\n    ...prevData,\n    hostedImageUrl: hostedUrl\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "1588fa1f-5442-41da-b14d-fbd9f949b0fd",
      "name": "OpenAI \u2014 Generate Platform Captions",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1952,
        224
      ],
      "parameters": {
        "resource": "chat"
      },
      "typeVersion": 1.4
    },
    {
      "id": "b37fbddd-84d6-4a15-9e20-938774c00c30",
      "name": "Code \u2014 Parse AI Captions Safely",
      "type": "n8n-nodes-base.code",
      "position": [
        2176,
        224
      ],
      "parameters": {
        "jsCode": "const aiResponse = $input.first().json;\nconst blogData = $('Code \u2014 Merge Hosted URL with Blog Data').first().json;\n\nlet captions = { linkedin: '', twitter: '' };\n\ntry {\n  const raw = aiResponse?.choices?.[0]?.message?.content || '{}';\n  // Remove markdown code fences if present\n  const cleaned = raw.replace(/```json|```/g, '').trim();\n  captions = JSON.parse(cleaned);\n} catch (e) {\n  // Fallback captions\n  captions.linkedin = `\ud83d\udcd6 New Blog Post: ${blogData.title}\\n\\n${blogData.summary.substring(0, 200)}...\\n\\nRead more: ${blogData.link}\\n\\n#Blog #Business #Content`;\n  captions.twitter = `\ud83d\udcd6 ${blogData.title} \u2014 ${blogData.link} #Blog #MustRead`;\n}\n\nreturn [{\n  json: {\n    ...blogData,\n    linkedinCaption: captions.linkedin,\n    twitterCaption: captions.twitter\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "17ac6813-23f2-4890-9e4d-3460f092dd0a",
      "name": "LinkedIn \u2014 Publish Post with Image",
      "type": "n8n-nodes-base.linkedIn",
      "position": [
        2400,
        128
      ],
      "parameters": {
        "text": "={{ $json.linkedinCaption }}",
        "person": "={{ $json.linkedinPersonUrn }}",
        "additionalFields": {
          "title": "={{ $json.title }}"
        },
        "shareMediaCategory": "IMAGE"
      },
      "typeVersion": 1
    },
    {
      "id": "8a292f70-edf9-4e68-9f30-3de84e18cb1c",
      "name": "Twitter/X \u2014 Publish Tweet",
      "type": "n8n-nodes-base.twitter",
      "position": [
        2400,
        352
      ],
      "parameters": {
        "text": "={{ $json.twitterCaption }}",
        "additionalFields": {}
      },
      "typeVersion": 1
    },
    {
      "id": "e809ff73-6948-4762-aaee-938510b94d74",
      "name": "Code \u2014 Success Logger",
      "type": "n8n-nodes-base.code",
      "position": [
        2624,
        224
      ],
      "parameters": {
        "jsCode": "const data = $('Code \u2014 Parse AI Captions Safely').first().json;\nconsole.log(`\u2705 Blog post published: ${data.title}`);\nconsole.log(`\ud83d\udd17 LinkedIn caption length: ${data.linkedinCaption?.length}`);\nconsole.log(`\ud83d\udc26 Twitter caption length: ${data.twitterCaption?.length}`);\nconsole.log(`\ud83d\uddbc\ufe0f Hosted image: ${data.hostedImageUrl}`);\nreturn [{ json: { status: 'success', title: data.title, timestamp: new Date().toISOString() } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "858b40b8-0549-4cbc-9464-ca9f2538737e",
      "name": "Upload a File",
      "type": "n8n-nodes-uploadtourl.uploadToUrl",
      "position": [
        1504,
        224
      ],
      "parameters": {},
      "credentials": {
        "uploadToUrlApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Upload a File": {
      "main": [
        [
          {
            "node": "Code \u2014 Merge Hosted URL with Blog Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "RSS Feed \u2014 Blog Monitor": {
      "main": [
        [
          {
            "node": "IF \u2014 Has Title & Cover Image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Twitter/X \u2014 Publish Tweet": {
      "main": [
        [
          {
            "node": "Code \u2014 Success Logger",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF \u2014 Has Title & Cover Image": {
      "main": [
        [
          {
            "node": "Code \u2014 Extract & Clean Blog Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code \u2014 Parse AI Captions Safely": {
      "main": [
        [
          {
            "node": "LinkedIn \u2014 Publish Post with Image",
            "type": "main",
            "index": 0
          },
          {
            "node": "Twitter/X \u2014 Publish Tweet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP \u2014 Fetch Cover Image Binary": {
      "main": [
        [
          {
            "node": "Upload a File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code \u2014 Extract & Clean Blog Data": {
      "main": [
        [
          {
            "node": "HTTP \u2014 Fetch Cover Image Binary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LinkedIn \u2014 Publish Post with Image": {
      "main": [
        [
          {
            "node": "Code \u2014 Success Logger",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI \u2014 Generate Platform Captions": {
      "main": [
        [
          {
            "node": "Code \u2014 Parse AI Captions Safely",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code \u2014 Merge Hosted URL with Blog Data": {
      "main": [
        [
          {
            "node": "OpenAI \u2014 Generate Platform Captions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}