AutomationFlowsAI & RAG › Draft and Publish Hacker News Github Posts to X and Linkedin with Openai and…

Draft and Publish Hacker News Github Posts to X and Linkedin with Openai and…

Original n8n title: Draft and Publish Hacker News Github Posts to X and Linkedin with Openai and Airtable

ByRedowan Ahmed Farhan @redowanfarhan on n8n.io

This workflow runs every 6 hours to find Hacker News front-page posts that link to GitHub repositories, generates Twitter/X and LinkedIn copy with OpenAI, queues and deduplicates drafts in Airtable, notifies you in Telegram for review, and then publishes to X and LinkedIn. Runs…

Cron / scheduled trigger★★★★☆ complexityAI-powered24 nodesHTTP RequestAirtableLinkedInTwitterOpenAITelegram
AI & RAG Trigger: Cron / scheduled Nodes: 24 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #17562 — we link there as the canonical source.

This workflow follows the Airtable → HTTP Request 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 →

Download .json
{
  "id": "zewdHLI58e4MieOE",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Social Media AI Agent - Telegram",
  "tags": [],
  "nodes": [
    {
      "id": "0ee55f36-da57-494c-81e2-a3429eb52785",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -16,
        -496
      ],
      "parameters": {
        "color": 7,
        "width": 580,
        "height": 1732,
        "content": "## \ud83d\udce3 AI-Powered Social Media Amplifier\n\nA scheduled agent that scans Hacker News for trending posts linking to GitHub projects, writes a tailored Twitter/X post and LinkedIn post for each one, and \u2014 after a human-in-the-loop pause \u2014 publishes both. An Airtable base acts as the dedupe log and content queue so the same project never gets posted twice.\n\n**Perfect for:** developer-tool marketers, DevRel teams, or solo builders who want a steady stream of on-brand social content about relevant open-source projects without manually trawling Hacker News every day.\n\n---\n\n## How it works\n\n1. **Schedule Trigger** \u2014 Runs every 6 hours.\n2. **Crawl HN Home** \u2014 Fetches the Hacker News front page HTML.\n3. **Extract Meta** \u2014 Parses the HTML with BeautifulSoup and keeps only submissions linking to `github.com`.\n4. **Search Item** \u2014 Checks Airtable for an existing record matching each post.\n5. **Merge** \u2014 Combines the HN items with the Airtable lookup results.\n6. **Filter Unposted Items** \u2014 Diffs the two inputs and keeps only posts that don't already have an Airtable record (the dedupe step).\n7. **Visit GH Page** \u2014 Fetches the actual GitHub repo page for each new item.\n8. **Convert HTML To Markdown** \u2014 Turns the GitHub page into readable markdown for the LLM.\n9. **Generate Content** \u2014 GPT-4o-mini writes a Twitter post and a LinkedIn post in JSON format, avoiding emojis and marketing buzzwords.\n10. **Validate Generate Content** \u2014 Checks the model actually returned both `twitter` and `linkedin` fields, re-parsing the string if needed, and drops malformed output.\n11. **Filter Errored** \u2014 Discards any item with an error field set.\n12. **Create Item** \u2014 Writes the new post pair into Airtable (Post/Title/Url/Tweet/LinkedIn columns).\n13. **Ping Me** \u2014 Sends the drafted tweet and LinkedIn post to a Telegram chat for review.\n14. **Wait for 5 mins before posting** \u2014 Pauses 5 minutes, giving a window to intervene before it goes live.\n15. **X** \u2014 Posts the tweet to X (Twitter).\n16. **LinkedIn** \u2014 Posts the LinkedIn version.\n17. **Update X Status** \u2014 Marks the Airtable record's `TDone` flag as posted.\n18. **Update L Status** \u2014 Marks the Airtable record's `LDone` flag as posted.\n19. **No Operation, do nothing** \u2014 Closes both branches once status is updated.\n\n---\n\n## Setup (~20 minutes)\n\n1. **Airtable** \u2014 Add a Personal Access Token credential to *Search Item*, *Create Item*, *Update X Status*, and *Update L Status*. All four point at a hardcoded base/table \u2014 create your own with `Post`, `Title`, `Url`, `Tweet`, `LinkedIn`, `TDone`, and `LDone` columns and repoint all four nodes.\n2. **OpenAI** \u2014 Add your API key in the *Generate Content* node.\n3. **Twitter/X** \u2014 Add OAuth2 credentials to the *X* node.\n4. **LinkedIn** \u2014 Add OAuth2 credentials to the *LinkedIn* node and replace the hardcoded `person` URN with your own.\n5. **Telegram** \u2014 Add a Bot API credential to *Ping Me* and replace the hardcoded `chatId` with your own chat ID.\n6. **Review window** \u2014 *Wait for 5 mins before posting* is your only chance to stop a bad post; increase it or swap in a manual-approval step for stricter control.\n> The *Extract Meta* code node installs `beautifulsoup4` and `simplejson` at runtime via micropip \u2014 no separate package install needed, but expect slower cold starts on first run."
      },
      "typeVersion": 1
    },
    {
      "id": "912d32b5-0ad2-46c3-8bd4-bb5505285aaa",
      "name": "Section: Trigger & Data Collection",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        720,
        -496
      ],
      "parameters": {
        "color": 5,
        "width": 1256,
        "height": 792,
        "content": "## 1\ufe0f\u20e3 Trigger & Data Collection\n\nThe **Schedule Trigger** fires every 6 hours, and **Crawl HN Home** downloads the Hacker News front page. **Extract Meta** parses that HTML with BeautifulSoup to pull out every submission linking to a GitHub repo. **Search Item** looks each post up in Airtable, and **Merge** joins the fresh HN results with those lookup results so duplicates can be spotted downstream."
      },
      "typeVersion": 1
    },
    {
      "id": "11707469-c178-4993-a42b-5acf53ca562a",
      "name": "Section: Dedupe, Fetch & AI Generation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2080,
        -464
      ],
      "parameters": {
        "color": 3,
        "width": 1400,
        "height": 728,
        "content": "## 2\ufe0f\u20e3 Dedupe, Fetch & AI Generation\n\n**Filter Unposted Items** drops any post that already has an Airtable record, keeping only genuinely new repos. **Visit GH Page** fetches the actual repository page, **Convert HTML To Markdown** turns it into clean markdown, and **Generate Content** (GPT-4o-mini) writes a Twitter post and LinkedIn post as JSON. **Validate Generate Content** confirms both fields came back correctly, and **Filter Errored** discards anything malformed."
      },
      "typeVersion": 1
    },
    {
      "id": "b03c090d-00e2-45fd-8d3a-50bf0328edf9",
      "name": "Section: Review Queue",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3552,
        -432
      ],
      "parameters": {
        "color": 6,
        "width": 660,
        "height": 664,
        "content": "## 3\ufe0f\u20e3 Review Queue\n\n**Create Item** logs the new tweet/LinkedIn pair into Airtable, **Ping Me** sends the drafted posts to Telegram for a quick human check, and **Wait for 5 mins before posting** holds the release for five minutes so a bad post can still be caught before it goes live."
      },
      "typeVersion": 1
    },
    {
      "id": "aa0b0eab-9f71-4789-91c3-e420c2a9ed8f",
      "name": "Section: Publish & Status Update",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4320,
        -448
      ],
      "parameters": {
        "color": 4,
        "width": 900,
        "height": 650,
        "content": "## 4\ufe0f\u20e3 Publish & Status Update\n\n**X** and **LinkedIn** publish the reviewed posts to each platform. **Update X Status** and **Update L Status** then flip the corresponding Airtable flags to mark the item as posted, and both paths finish at **No Operation, do nothing**."
      },
      "typeVersion": 1
    },
    {
      "id": "72710477-cc7c-41e4-b023-450301902085",
      "name": "Crawl HN Home",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1136,
        48
      ],
      "parameters": {
        "url": "https://news.ycombinator.com/",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "fullResponse": true
            }
          }
        }
      },
      "executeOnce": true,
      "typeVersion": 4.2,
      "alwaysOutputData": true
    },
    {
      "id": "f5e45ce3-593b-44bd-b849-973fe2cd2a94",
      "name": "Extract Meta",
      "type": "n8n-nodes-base.code",
      "position": [
        1408,
        48
      ],
      "parameters": {
        "language": "python",
        "pythonCode": "# Import necessary modules\nimport asyncio\nimport micropip\n\n# Define an asynchronous function to install packages\nasync def install_packages():\n await micropip.install(\"beautifulsoup4\")\n await micropip.install(\"simplejson\")\n\n# Run the asynchronous package installation\nasyncio.get_event_loop().run_until_complete(install_packages())\n\n# Now, import the installed packages\nimport simplejson as json\nfrom bs4 import BeautifulSoup\n\n# Retrieve the HTML content from the first item in the input\n# Assuming n8n passes data as a list of items, each with a 'json' key\nhtml_content = items[0].get('json', {}).get('data', '')\n\n# Initialize BeautifulSoup with the HTML content\nsoup = BeautifulSoup(html_content, 'html.parser')\n\n# Initialize a list to store metadata of GitHub posts\ngithub_posts = []\n\n# Find all 'tr' elements with class 'athing submission'\nposts = soup.find_all('tr', class_='athing submission')\n\nfor post in posts:\n post_id = post.get('id')\n title_line = post.find('span', class_='titleline')\n if not title_line:\n continue # Skip if titleline is not found\n\n # Extract the title and URL\n title_tag = title_line.find('a')\n if not title_tag:\n continue # Skip if title tag is not found\n\n title = title_tag.get_text(strip=True)\n url = title_tag.get('href', '')\n\n # Check if the URL is a GitHub link\n if 'github.com' not in url.lower():\n continue # Skip if not a GitHub link\n\n # Extract the site domain (e.g., github.com/username/repo)\n site_bit = title_line.find('span', class_='sitebit comhead')\n site = site_bit.find('span', class_='sitestr').get_text(strip=True) if site_bit else ''\n\n # The subtext is in the next 'tr' element\n subtext_tr = post.find_next_sibling('tr')\n if not subtext_tr:\n continue # Skip if subtext row is not found\n\n subtext_td = subtext_tr.find('td', class_='subtext')\n if not subtext_td:\n continue # Skip if subtext td is not found\n\n # Extract score\n score_span = subtext_td.find('span', class_='score')\n score = score_span.get_text(strip=True) if score_span else '0 points'\n\n # Extract author\n author_a = subtext_td.find('a', class_='hnuser')\n author = author_a.get_text(strip=True) if author_a else 'unknown'\n\n # Extract age\n age_span = subtext_td.find('span', class_='age')\n age_a = age_span.find('a') if age_span else None\n age = age_a.get_text(strip=True) if age_a else 'unknown'\n\n # Extract comments\n comments_a = subtext_td.find_all('a')[-1] if subtext_td.find_all('a') else None\n comments_text = comments_a.get_text(strip=True) if comments_a else '0 comments'\n\n # Construct the Hacker News URL\n hn_url = f\"https://news.ycombinator.com/item?id={post_id}\"\n\n # Compile the metadata\n post_metadata = {\n 'Post': post_id,\n 'title': title,\n 'url': url,\n 'site': site,\n 'score': score,\n 'author': author,\n 'age': age,\n 'comments': comments_text,\n 'hn_url': hn_url\n }\n\n # Append to the list of GitHub posts\n github_posts.append(post_metadata)\n\n# Prepare the output for n8n\noutput = [{'json': post} for post in github_posts]\n\n# Return the output\nreturn output\n"
      },
      "executeOnce": true,
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "84baebe8-4ffa-4c6f-85a3-08534070b59d",
      "name": "Filter Unposted Items",
      "type": "n8n-nodes-base.code",
      "position": [
        2144,
        -80
      ],
      "parameters": {
        "jsCode": "const items = [];\n\n// Step 1: Collect all Post IDs from input1 items (those with 'id')\nconst processedPosts = new Set(\n $input.all()\n .filter(item => item.json.id)\n .map(item => item.json.Post)\n);\n\n// Step 2: Iterate over all items and filter out duplicates\nfor (const item of $input.all()) {\n \n // Only process items without 'id' (input2 items)\n if(!item.json.id){\n \n // Check if the Post ID is already processed\n if(!processedPosts.has(item.json.Post) && item.json.Post!=undefined){\n items.push(item);\n }\n }\n}\n\nreturn items;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "4adf04ce-f4eb-4d42-8ee5-23c3dd50b129",
      "name": "Visit GH Page",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2336,
        -240
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "ddf5637a-ca6f-41f7-8f35-86c67606494e",
      "name": "Convert HTML To Markdown",
      "type": "n8n-nodes-base.markdown",
      "position": [
        2528,
        -16
      ],
      "parameters": {
        "html": "={{ $json.data }}",
        "options": {}
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "a9595e11-b428-431b-a211-7e86277e057f",
      "name": "Filter Errored",
      "type": "n8n-nodes-base.filter",
      "position": [
        3136,
        -224
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "7776cb97-e02d-418e-a168-612bf92d4160",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              },
              "leftValue": "={{ $json.error }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "10e0e148-f323-4ab8-8be0-0db6aee502bf",
      "name": "No Operation, do nothing",
      "type": "n8n-nodes-base.noOp",
      "position": [
        4960,
        -176
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "3422732a-4d94-4ad3-964f-938199288b9e",
      "name": "Update X Status",
      "type": "n8n-nodes-base.airtable",
      "position": [
        4672,
        -272
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "app7fh2kmMzPKS4RZ",
          "cachedResultUrl": "https://airtable.com/app7fh2kmMzPKS4RZ",
          "cachedResultName": "Twitter Agent"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblf0cODJFdvDj7vU",
          "cachedResultUrl": "https://airtable.com/app7fh2kmMzPKS4RZ/tblf0cODJFdvDj7vU",
          "cachedResultName": "My Tweets"
        },
        "columns": {
          "value": {
            "id": "={{ $('Create Item').item.json.id }}",
            "TDone": true
          },
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true
            },
            {
              "id": "Post",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "Post",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Title",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Url",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "Url",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Tweet",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "Tweet",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "LinkedIn",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "LinkedIn",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Last Modified",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "Last Modified",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "TDone",
              "type": "boolean",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "TDone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "LDone",
              "type": "boolean",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "LDone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ]
        },
        "options": {
          "typecast": true
        },
        "operation": "update"
      },
      "typeVersion": 2.1
    },
    {
      "id": "270916df-4a43-4169-a75d-4f257ec56c68",
      "name": "LinkedIn",
      "type": "n8n-nodes-base.linkedIn",
      "position": [
        4432,
        48
      ],
      "parameters": {
        "text": "={{ $('Filter Errored').item.json.message.content.linkedin }}",
        "person": "afi4Hy9wlI",
        "additionalFields": {}
      },
      "typeVersion": 1
    },
    {
      "id": "7f04202c-437d-4f92-8377-c04bcb2c8ead",
      "name": "Update L Status",
      "type": "n8n-nodes-base.airtable",
      "position": [
        4704,
        -16
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "app7fh2kmMzPKS4RZ",
          "cachedResultUrl": "https://airtable.com/app7fh2kmMzPKS4RZ",
          "cachedResultName": "Twitter Agent"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblf0cODJFdvDj7vU",
          "cachedResultUrl": "https://airtable.com/app7fh2kmMzPKS4RZ/tblf0cODJFdvDj7vU",
          "cachedResultName": "My Tweets"
        },
        "columns": {
          "value": {
            "id": "={{ $('Create Item').item.json.id }}",
            "LDone": true
          },
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true
            },
            {
              "id": "Post",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "Post",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Title",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Url",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "Url",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Tweet",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "Tweet",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "LinkedIn",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "LinkedIn",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Last Modified",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "Last Modified",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "TDone",
              "type": "boolean",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "TDone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "LDone",
              "type": "boolean",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "LDone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ]
        },
        "options": {
          "typecast": true
        },
        "operation": "update"
      },
      "typeVersion": 2.1
    },
    {
      "id": "6aec3617-793e-4866-a148-3f4ccd5ebfb5",
      "name": "Search Item",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1584,
        -272
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "app7fh2kmMzPKS4RZ",
          "cachedResultUrl": "https://airtable.com/app7fh2kmMzPKS4RZ",
          "cachedResultName": "Twitter Agent"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblf0cODJFdvDj7vU",
          "cachedResultUrl": "https://airtable.com/app7fh2kmMzPKS4RZ/tblf0cODJFdvDj7vU",
          "cachedResultName": "My Tweets"
        },
        "options": {
          "fields": [
            "Title",
            "Url",
            "Tweet",
            "Date",
            "Post"
          ]
        },
        "operation": "search",
        "filterByFormula": "={Post}= {{ $json.Post }}"
      },
      "typeVersion": 2.1,
      "alwaysOutputData": true
    },
    {
      "id": "7fbe7afc-c55b-4bd0-97e3-cc92a589d427",
      "name": "Create Item",
      "type": "n8n-nodes-base.airtable",
      "position": [
        3616,
        48
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "app7fh2kmMzPKS4RZ",
          "cachedResultUrl": "https://airtable.com/app7fh2kmMzPKS4RZ",
          "cachedResultName": "Twitter Agent"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblf0cODJFdvDj7vU",
          "cachedResultUrl": "https://airtable.com/app7fh2kmMzPKS4RZ/tblf0cODJFdvDj7vU",
          "cachedResultName": "My Tweets"
        },
        "columns": {
          "value": {
            "Url": "={{ $('Filter Unposted Items').item.json.url }}",
            "Post": "={{ $('Filter Unposted Items').item.json.Post }}",
            "Title": "={{ $('Filter Unposted Items').item.json.title }}",
            "Tweet": "={{ $json.message.content.twitter }}",
            "LinkedIn": "={{ $json.message.content.linkedin }}"
          },
          "schema": [
            {
              "id": "Post",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Post",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Title",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Url",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Url",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Tweet",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Tweet",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "LinkedIn",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "LinkedIn",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": []
        },
        "options": {},
        "operation": "create"
      },
      "typeVersion": 2.1
    },
    {
      "id": "f1698854-bc62-4efc-8442-03c0761b2a36",
      "name": "X",
      "type": "n8n-nodes-base.twitter",
      "onError": "continueRegularOutput",
      "position": [
        4432,
        -272
      ],
      "parameters": {
        "text": "={{ $('Filter Errored').item.json.message.content.twitter }}",
        "additionalFields": {}
      },
      "executeOnce": false,
      "typeVersion": 2
    },
    {
      "id": "2f0829b6-54a3-4d2d-98b1-e915dd7d3b35",
      "name": "Validate Generate Content",
      "type": "n8n-nodes-base.code",
      "onError": "continueRegularOutput",
      "position": [
        2944,
        -48
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "if ($json.message.content.twitter && $json.message.content.linkedin) {\n \n return $json;\n} else {\n\n const parsedContent = JSON.parse($json.message.content);\n if ($json.message.content.twitter && $json.message.content.linkedin) {\n return parsedContent;\n }\n\n console.log(\"Invalid formatting\")\n return {}\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "c0f46520-d573-4332-9f25-d5fa2c8126d2",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        896,
        48
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 6
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "b9d723d1-9fd8-44ae-97ae-1e3c4fbdd997",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        1840,
        -160
      ],
      "parameters": {},
      "typeVersion": 3,
      "alwaysOutputData": true
    },
    {
      "id": "b40a6eed-1983-496a-aa64-a3efc6a68b6d",
      "name": "Generate Content",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        2640,
        -224
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are an AI-powered social media assistant specialized in crafting short-form, engaging posts for Twitter and LinkedIn. Your tone should blend the enthusiasm of a Tech Evangelist with the narrative depth of a Storyteller. The goal is to highlight technological and open-source projects in a friendly, forward-thinking manner, connecting them to real-world use cases. \n\nGuidelines:\n1. Output must be in JSON with separate fields for \u201ctwitter\u201d and \u201clinkedin.\u201d\n2. Do not include emojis or marketing buzzwords (\u201ccutting-edge,\u201d \u201cdisruptive,\u201d etc.).\n3. Write naturally and concisely. Avoid overly formal or robotic language.\n4. Twitter posts must be under 280 characters (including spaces and URL).\n5. LinkedIn posts should be slightly longer, yet still succinct, and focus on storytelling and real-world applications.\n6. Provide a single call-to-action in each post.\n7. Do not imply ownership of the project unless explicitly stated.\n8. Maintain a professional yet approachable tone in both outputs.\n"
            },
            {
              "content": "=Using the following details, generate two posts\u2014one for Twitter and one for LinkedIn\u2014incorporating an enthusiastic yet narrative-driven style:\n\nTitle: {{ $('Filter Unposted Items').item.json.title }}\nDetails in markdown: {{ $json.data }}\nRepository Link: {{ $('Filter Unposted Items').item.json.url }} (this is the actual link you want to be inserted)\n\nConstraints:\n- No emojis.\n- Keep the Twitter post under 280 characters (including the link).\n- Use a friendly, forward-thinking tone that weaves in a short narrative where possible.\n- Highlight how the project solves a real problem or benefits the user.\n- End each post with one clear CTA (e.g., \u201cCheck it out!\u201d or \u201cLearn more.\u201d).\n- **Ensure the tone is neutral and does not imply personal involvement** (e.g., avoid phrases like \"my journey\" or \"I found it fascinating\").\n- **LinkedIn post should be more detailed**: Provide context, explain the key features, highlight how it can be useful to different audiences, and elaborate on the problem it solves or the impact it can have.\n- Output your response in JSON with the structure:\n```json\n{\n \"twitter\": \"Your Twitter post here\",\n \"linkedin\": \"Your LinkedIn post here\"\n}\n"
            }
          ]
        },
        "jsonOutput": true
      },
      "typeVersion": 1.7,
      "alwaysOutputData": true
    },
    {
      "id": "c61e5c0a-9dd6-4918-afb6-6b3ca014555e",
      "name": "Ping Me",
      "type": "n8n-nodes-base.telegram",
      "position": [
        3760,
        -192
      ],
      "parameters": {
        "text": "=Hi There, here is your readymade tweet - \n\n {{ $json.fields.Tweet }}\n\nAnd your readymade LinkedIn post -\n\n {{ $json.fields.LinkedIn }}\n\n",
        "chatId": "123456789",
        "additionalFields": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "a43a2d14-9a5a-46cf-8cde-674f6b68f638",
      "name": "Wait for 5 mins before posting",
      "type": "n8n-nodes-base.wait",
      "position": [
        3920,
        48
      ],
      "parameters": {
        "unit": "minutes"
      },
      "typeVersion": 1.1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "f0e09ba2-dd88-41d4-afbc-c431696b7d77",
  "nodeGroups": [],
  "connections": {
    "X": {
      "main": [
        [
          {
            "node": "Update X Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Filter Unposted Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ping Me": {
      "main": [
        [
          {
            "node": "Wait for 5 mins before posting",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LinkedIn": {
      "main": [
        [
          {
            "node": "Update L Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Item": {
      "main": [
        [
          {
            "node": "Ping Me",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Item": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Meta": {
      "main": [
        [
          {
            "node": "Search Item",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Crawl HN Home": {
      "main": [
        [
          {
            "node": "Extract Meta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Visit GH Page": {
      "main": [
        [
          {
            "node": "Convert HTML To Markdown",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Errored": {
      "main": [
        [
          {
            "node": "Create Item",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update L Status": {
      "main": [
        [
          {
            "node": "No Operation, do nothing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update X Status": {
      "main": [
        [
          {
            "node": "No Operation, do nothing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Content": {
      "main": [
        [
          {
            "node": "Validate Generate Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Crawl HN Home",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Unposted Items": {
      "main": [
        [
          {
            "node": "Visit GH Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert HTML To Markdown": {
      "main": [
        [
          {
            "node": "Generate Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Generate Content": {
      "main": [
        [
          {
            "node": "Filter Errored",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for 5 mins before posting": {
      "main": [
        [
          {
            "node": "X",
            "type": "main",
            "index": 0
          },
          {
            "node": "LinkedIn",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow runs every 6 hours to find Hacker News front-page posts that link to GitHub repositories, generates Twitter/X and LinkedIn copy with OpenAI, queues and deduplicates drafts in Airtable, notifies you in Telegram for review, and then publishes to X and LinkedIn. Runs…

Source: https://n8n.io/workflows/17562/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Social Media AI Agent - Telegram. Uses httpRequest, markdown, noOp, airtable. Scheduled trigger; 26 nodes.

HTTP Request, Airtable, LinkedIn +3
AI & RAG

> Reach out to me for any setup help/consulting.

HTTP Request, Airtable, LinkedIn +3
AI & RAG

This workflow is for beauty salons who want consistent, high‑quality social media content without writing every post manually. It also suits agencies and automation builders who manage multiple beauty

Telegram, Google Sheets Trigger, Agent +26
AI & RAG

Who Is This For?

Telegram, Google Sheets Trigger, Lm Chat Mistral Cloud +17
AI & RAG

22-Automate_Multi_Platform_Social_Media_Content_Creation. Uses outputParserStructured, lmChatGoogleGemini, lmChatOpenAi, httpRequest. Event-driven trigger; 57 nodes.

Output Parser Structured, Google Gemini Chat, OpenAI Chat +11