AutomationFlowsSocial Media › Generate social posts from GitHub pushes to Twitter and LinkedIn

Generate social posts from GitHub pushes to Twitter and LinkedIn

Generate social posts from GitHub pushes to Twitter and LinkedIn. Uses github, noOp, chainLlm, lmChatOpenAi. Webhook trigger; 18 nodes.

Webhook trigger★★★★☆ complexityAI-powered18 nodesGithubChain LlmLm Chat Open AiOutput Parser StructuredTwitterLinked In
Social Media Trigger: Webhook Nodes: 18 Complexity: ★★★★☆ AI nodes: yes

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": "zMl1xguA65ATPxMh",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Generate social posts from GitHub pushes to Twitter and LinkedIn",
  "tags": [],
  "nodes": [
    {
      "id": "29f468dd-8822-4de7-9bf7-0205e8b51b12",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -640,
        -144
      ],
      "parameters": {
        "path": "70b80513-b93c-4749-9a06-e78d4f1f2d23",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2
    },
    {
      "id": "a991ef0a-48ef-4ccf-98c6-587edddafa96",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        400,
        -80
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "a19b66f5-0927-4c59-8343-f2cea29feb5e",
      "name": "Get README",
      "type": "n8n-nodes-base.github",
      "position": [
        64,
        -208
      ],
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "jorge210488"
        },
        "filePath": "README.md",
        "resource": "file",
        "operation": "get",
        "repository": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $json.body.repository.name }}"
        },
        "additionalParameters": {}
      },
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "b3ae8e15-dd37-4207-a78c-4b18b72c9cb8",
      "name": "Get CHANGELOG",
      "type": "n8n-nodes-base.github",
      "position": [
        64,
        32
      ],
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "jorge210488"
        },
        "filePath": "CHANGELOG.md",
        "resource": "file",
        "operation": "get",
        "repository": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $json.body.repository.name }}"
        },
        "additionalParameters": {}
      },
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "9229a69b-fe35-4a25-a8d8-bc3e1f6d8894",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        -448,
        -144
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "fb7ff717-d8a0-44ed-9053-1e88d50f88de",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{\n  ($json.body.commits ?? []).some(c =>\n    [ ...(c.added ?? []), ...(c.modified ?? []), ...(c.removed ?? []) ]\n      .some(f => String(f).toLowerCase().includes('changelog'))\n  )\n}}",
              "rightValue": "="
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "4e9ebbbd-9c37-4dbe-a9b6-dc0f72a85877",
      "name": "No Operation, do nothing",
      "type": "n8n-nodes-base.noOp",
      "position": [
        -256,
        48
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "e35435d4-f3f8-44f1-ac78-21efda7076ac",
      "name": "Aggregate",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        560,
        -80
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "82c95a95-4a00-405d-a826-9c387a3f76be",
      "name": "Basic LLM Chain",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        832,
        -80
      ],
      "parameters": {
        "text": "=README: {{ $json.data[0].data }}\nCHANGELOG: {{ $json.data[1].data }}",
        "batching": {},
        "messages": {
          "messageValues": [
            {
              "message": "=# Role\n\nYou are a precise content generator for social media.\nYour input will be a JSON object with two string fields:\n\n* `README`: full project README in Markdown.\n* `CHANGELOG`: full changelog in Markdown.\n\n## Your task\n\nProduce **one JSON object** with exactly two keys:\n\n```json\n{\n  \"twitter\": \"<tweet in English, <=280 chars including the GitHub link>\",\n  \"linkedin\": \"<LinkedIn post in English, long-form>\"\n}\n```\n\nNo extra keys, no surrounding text, no Markdown fences.\n\n## Parsing rules (do not hallucinate)\n\n1. **Repository URL (required in both posts):**\n\n   * Extract the **first** URL that matches a GitHub repo pattern from `CHANGELOG` using a regex like:\n     `https?://github\\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+`\n   * If not found in `CHANGELOG`, search `README` with the same regex.\n   * If still not found, **do not fabricate** a URL; instead, use the literal placeholder `https://github.com/REPO_MISSING`. (Keep generating the posts.)\n\n2. **Project name:**\n\n   * Prefer the first H1 (`# Title`) in `README`. If not present, infer a concise name from the first heading found. If impossible, use a short neutral name like \u201cThis project\u201d.\n\n3. **First release or update determination:**\n\n   * From `CHANGELOG`, detect if this is the **first or only version** (e.g., version numbers like `v1.0.0`, \"Initial release\", or only one version section present).\n   * If first/only version \u2192 the post should clearly state it's a **new project/app/development** (e.g., \u201cNew project\u201d, \u201cLaunching a new application\u201d).\n   * If not first version \u2192 the post should state it\u2019s an **update or improvement** (e.g., \u201cUpdating the project X\u201d, \u201cImproving the application X\u201d).\n\n4. **Description & features:**\n\n   * Derive a clear, one-sentence value proposition from `README`\u2019s Description/Overview.\n   * Collect 4\u20137 key capabilities/benefits (bullets for LinkedIn).\n\n5. **Tech stack:**\n\n   * From `README`, extract notable technologies (backend, frontend, infra/services).\n   * Examples to detect if present: Django, DRF, Celery, Redis, Channels/WebSockets, Next.js, React, TypeScript, Tailwind CSS, Zustand, Framer Motion, Stripe, OpenAI, OAuth/Google, SMTP/Email, Docker, Docker Compose.\n   * For **Twitter**, mention only the **3\u20134 most central** technologies.\n   * For **LinkedIn**, you may group by Backend / Frontend / Infra.\n\n## LinkedIn post (English, long-form)\n\n* Tone: professional, enthusiastic, concise sentences; **no emojis**.\n* Structure (use short paragraphs and bullet points):\n\n  * Hook:\n\n    * If first release \u2192 start with \u201cNew project: \u2026\u201d or similar phrasing.\n    * If update \u2192 start with \u201cUpdating the project \\[name]: \u2026\u201d or similar phrasing.\n  * What it does: 1 short paragraph.\n  * Key features/benefits: 4\u20137 bullets.\n  * Tech stack (grouped): bullets for Backend, Frontend, Infra/Services.\n  * Motivational developer line (one sentence, non-cheesy).\n  * Call to action with the **repo link** (exactly once).\n* Hashtags: **8\u201312** relevant tags, all lowercase, no spaces (use hyphens only if part of the tech brand). Prioritize technologies and domain, e.g.:\n  `#python #django #nextjs #typescript #tailwindcss #stripe #openai #celery #redis #websockets #ai #saas #startups #webdevelopment`\n  (Pick only those truly present; don\u2019t invent.)\n\n## Twitter post (English, max 280 chars including link)\n\n* Format:\n\n  * If first release \u2192 short sentence like \u201cNew project: \u2026\u201d or similar.\n  * If update \u2192 short sentence like \u201cUpdating project \\[name]: \u2026\u201d or similar.\n* Describe what the app does + **repo link** (once).\n* Mention **3\u20134** main technologies inline (e.g., Django, Next.js, Stripe, OpenAI).\n* **2\u20133 hashtags max**, chosen from the most relevant.\n* **No emojis.**\n* Length enforcement: if >280 chars, iteratively shorten by (in order): remove adjectives, compress wording, then drop one hashtag at a time until \u2264280. Never drop the repo link.\n\n## Output formatting\n\n* Return a **single JSON object** with keys `\"twitter\"` and `\"linkedin\"`.\n* Escape quotes properly.\n* Do **not** add explanations, markdown, or extra whitespace before/after the JSON.\n* Do not include any links other than the repo link.\n\n## Safety & accuracy\n\n* Never fabricate tech or features not present in the inputs.\n* If some sections are missing in inputs, omit that detail gracefully.\n* Keep everything in **English** except hashtags which are typically lowercase English tokens.\n* Do not output dates/releases unless explicitly present.\n* Maintain a positive, credible tone.\n"
            }
          ]
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.7
    },
    {
      "id": "927afa09-65bf-4c62-955c-42a3bb098f28",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        832,
        80
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "43f2f142-8319-445f-80a9-61c56ae6ddad",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        1008,
        80
      ],
      "parameters": {
        "jsonSchemaExample": "{\n  \"twitter\": \"tweet in English, <=280 chars including the GitHub link\",\n  \"linkedin\": \"LinkedIn post in English, long-form\"\n}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "c2454817-d07d-45e0-8c5f-03a68c619eb8",
      "name": "Post tweet",
      "type": "n8n-nodes-base.twitter",
      "position": [
        1248,
        48
      ],
      "parameters": {
        "text": "={{ $json.output.twitter }}",
        "additionalFields": {}
      },
      "credentials": {
        "twitterOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "944f1559-f79e-4034-b70f-0246c8b40a5f",
      "name": "LinkedIn",
      "type": "n8n-nodes-base.linkedIn",
      "position": [
        1248,
        -160
      ],
      "parameters": {
        "text": "={{ $json.output.linkedin }}",
        "person": "nUdV-_cHkk",
        "additionalFields": {}
      },
      "credentials": {
        "linkedInOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "d64cf42e-40b0-4db8-9748-774751840a14",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -720,
        -720
      ],
      "parameters": {
        "width": 660,
        "height": 900,
        "content": "# GitHub Push \u2192 README & CHANGELOG check\n\n## 1) Webhook (receive push from GitHub)\n* **Node:** Webhook (POST)\n* **GitHub setup:** Repository \u2192 *Settings \u2192 Webhooks \u2192 Add webhook*\n  * **Payload URL:** `https://<your-n8n-domain>/webhook/github/push`\n  * **Content type:** `application/json`\n  * **Event:** *Just the push event*\n  * **Secret:** optional\n  * **Branches:** choose specific branches or send all\n## 2) IF (filter)\n* **Node:** IF\n* **Checks:** push contains `README` and `CHANGELOG` in *added* or *modified* files\n* **True:** continue\n* **False:** do nothing (stop)\n"
      },
      "typeVersion": 1
    },
    {
      "id": "b08176e7-283f-4ed3-8c54-c90daeb05d59",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        48,
        -720
      ],
      "parameters": {
        "color": 4,
        "width": 640,
        "height": 920,
        "content": "# GitHub \u2192 Extract \u2192 Merge/Aggregate\n\n## 1) GitHub (Get Repository File)\n* **Credentials:** GitHub OAuth2 or PAT with read access.\n* **Owner/Repo/Branch:** from webhook payload (`body.repository`, branch from `body.ref`).\n* **File Path:** `README.md` and `CHANGELOG.md` (use two GitHub nodes).\n* **Output:** binary files.\n## 2) Extract from File (text)\n* **Operation:** `text`.\n* **Input:** binary from each GitHub node.\n* **Output:** plain text for README and CHANGELOG.\n## 3) Merge & Aggregate\n* **Merge:** *Wait for Both* (combine README + CHANGELOG into one item).\n* **Aggregate:** `aggregateAllItemData` (single item with `documents` list holding both contents).\n"
      },
      "typeVersion": 1
    },
    {
      "id": "90e42f76-4ca3-4e6d-902c-2b4f1e29c0a4",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        752,
        -720
      ],
      "parameters": {
        "width": 720,
        "height": 920,
        "content": "# LLM \u2192 Post to Twitter & LinkedIn\n\n## 1) LLM (OpenAI \u2192 JSON)\n* Nodes: OpenAI Chat Model \u2192 Basic LLM Chain (+ Structured Output Parser)\n* Input: aggregated README + CHANGELOG\n* Output: JSON with `\"twitter\"` and `\"linkedin\"`\n## 2) Publish\n* **Twitter node**\n  * Auth: credentials\n* **LinkedIn node (Person)**\n  * Auth: credentials\n  * Required scope: `w_member_social`, `openid`\n"
      },
      "typeVersion": 1
    },
    {
      "id": "3f31d48b-c0cc-4140-9bd8-02f4267f7f65",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1520,
        -720
      ],
      "parameters": {
        "color": 5,
        "width": 780,
        "height": 900,
        "content": "# Generate social posts from GitHub pushes to Twitter and LinkedIn\nOn each GitHub *push*, this workflow checks if the commit set includes **README.md** and **CHANGELOG.md**, fetches both files, lets an **LLM** generate a Twitter and LinkedIn post, then publishes to **Twitter** and **LinkedIn (Person)**.\n## Apps & Nodes\n* **Trigger:** Webhook\n* **Logic:** IF, Merge, Aggregate\n* **GitHub:** Get Repository File (\u00d72)\n* **Files:** Extract from File (text) (\u00d72)\n* **AI:** OpenAI Chat Model \u2192 LLM Chain (+ Structured Output Parser)\n* **Publish:** Twitter, LinkedIn (Person)\n## Prerequisites\n* **GitHub:** OAuth2 or PAT with repo read.\n* **OpenAI:** API key.\n* **Twitter:** OAuth2 app with *Read and Write*; scopes `tweet.read tweet.write users.read offline.access`.\n* **LinkedIn (Person):** OAuth2 credentials; **required scope:** `w_member_social`, `openid`.\n## Setup\n1. **GitHub Webhook:** Repo \u2192 *Settings \u2192 Webhooks*\n\n   * Payload URL: `https://<your-n8n-domain>/webhook/github/push`\n   * Content type: `application/json` \u2022 Event: *Push* \u2022 Secret (optional) \u2022 Branches as needed.\n2. **Credentials:** Connect GitHub, OpenAI, Twitter, and LinkedIn (Person).\n## How it Works\n1. **Webhook** receives GitHub push payload.\n2. **IF** checks that `README` and `CHANGELOG` appear in *added/modified*.\n3. **GitHub (Get Repository File)** pulls `README.md` and `CHANGELOG.md`.\n4. **Extract from File (text)** converts both binaries to text.\n5. **Merge & Aggregate** combines into one item with both contents.\n6. **LLM (OpenAI + Parser)** returns a JSON with `twitter` and `linkedin`.\n7. **Twitter** posts the tweet.\n8. **LinkedIn (Person)** posts the LinkedIn text.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "95134ca6-76d8-49a2-ae2c-6f1d7121c9a6",
      "name": "Extract from File README",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        224,
        -208
      ],
      "parameters": {
        "options": {},
        "operation": "text"
      },
      "typeVersion": 1
    },
    {
      "id": "a61fd00a-34f2-4e8b-bdf5-7e9c686f58ed",
      "name": "Extract from File CHANGELOG",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        224,
        32
      ],
      "parameters": {
        "options": {},
        "operation": "text"
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "78f3c247-4dc6-474b-8a51-3a55a072dd92",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Get CHANGELOG",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get README",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Operation, do nothing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Aggregate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate": {
      "main": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get README": {
      "main": [
        [
          {
            "node": "Extract from File README",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get CHANGELOG": {
      "main": [
        [
          {
            "node": "Extract from File CHANGELOG",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Basic LLM Chain": {
      "main": [
        [
          {
            "node": "Post tweet",
            "type": "main",
            "index": 0
          },
          {
            "node": "LinkedIn",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Extract from File README": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Extract from File CHANGELOG": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

About this workflow

Generate social posts from GitHub pushes to Twitter and LinkedIn. Uses github, noOp, chainLlm, lmChatOpenAi. Webhook trigger; 18 nodes.

Source: https://github.com/ScraperNode/awesome-n8n-templates/blob/main/templates/ai-and-llm/7562-auto-generate-social-posts-from-github-readmechangelog-updates-with-gpt-4o-and-o/workflow.json — original creator credit. Request a take-down →

More Social Media workflows → · Browse all categories →