{
  "id": "VP8zZEgFO4T5KxRH",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI Reply Drafts for LinkedIn Comments",
  "tags": [],
  "nodes": [
    {
      "id": "44ccaf84-03ab-4d6b-84b3-0b84126a99b7",
      "name": "Every 30 Minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        0,
        0
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 30
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "dab84dbc-3ec9-476a-8e0b-858bc5634572",
      "name": "Fetch LinkedIn Notification Emails",
      "type": "n8n-nodes-base.gmail",
      "position": [
        224,
        0
      ],
      "parameters": {
        "limit": 25,
        "simple": false,
        "filters": {
          "q": "from:linkedin.com in:anywhere newer_than:3d {subject:\"commented on your post\" subject:\"Beitrag kommentiert\" subject:\"replied to your comment\" subject:\"Kommentar geantwortet\"}"
        },
        "options": {},
        "operation": "getAll"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "3edc7d17-8e91-43e8-9495-b45a901ed081",
      "name": "Parse Comment Details",
      "type": "n8n-nodes-base.code",
      "position": [
        448,
        0
      ],
      "parameters": {
        "jsCode": "\n// Parse LinkedIn comment notification emails into structured records.\n// The Gmail node (simple=false) already provides: text, subject, from, messageId, date.\nconst out = [];\nconst seen = new Set();\nfor (const item of $input.all()) {\n  const j = item.json || {};\n  const subject = j.subject || '';\n  const from = typeof j.from === 'string' ? j.from : JSON.stringify(j.from || '');\n  if (!/linkedin\\.com/i.test(from)) continue;\n\n  // English + German subject patterns. Extend for other languages.\n  const m = subject.match(/^(.*?)\\s+(commented on your post|hat (?:Ihren|deinen) Beitrag kommentiert|replied to your comment|hat auf (?:Ihren|deinen) Kommentar geantwortet)/i);\n  if (!m) continue;\n  const name = m[1].trim();\n\n  const text = j.text || '';\n  const paras = text.split(/\\r?\\n\\s*\\r?\\n/).map(p => p.trim()).filter(Boolean);\n  // paras[0] = headline (same as subject), then the comment (quoted), then the post preview.\n  let comment = '';\n  const postParts = [];\n  for (let i = 1; i < paras.length; i++) {\n    const p = paras[i];\n    if (/^(Anzeigen|Mitdiskutieren|View |Join the|Diese E-Mail|This email)/.test(p)) break;\n    if (!comment) { comment = p.replace(/^[\\u201E\\u201C\"']+|[\\u201C\\u201D\"']+$/g, '').trim(); continue; }\n    postParts.push(p);\n  }\n  const postTitle = postParts.join(' ').replace(/\\s+/g, ' ').slice(0, 200);\n\n  const urlMatch = text.match(/(https:\\/\\/www\\.linkedin\\.com\\/(?:comm\\/)?feed\\/update\\/\\S+)/);\n  const postUrl = urlMatch ? urlMatch[1] : null;\n\n  const msgId = j.messageId || j.id || null;\n  if (!msgId || seen.has(msgId)) continue;\n  seen.add(msgId);\n\n  let receivedAt = null;\n  if (j.date) { const d = new Date(j.date); if (!isNaN(d)) receivedAt = d.toISOString(); }\n\n  out.push({ json: {\n    commenter_name: name,\n    comment_text: comment || '',\n    post_title: postTitle || '',\n    post_url: postUrl || '',\n    mail_message_id: msgId,\n    received_at: receivedAt || '',\n  }});\n}\nreturn out;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "320c51eb-8b43-416a-b0b7-efd65dc9f1da",
      "name": "Read Logged Comments",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        672,
        0
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 434307311,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1_4WZ6yr9sFzNJmWGeipGu7u099iKDqiSSLdTp5-szoc/edit#gid=434307311",
          "cachedResultName": "Comments"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1_4WZ6yr9sFzNJmWGeipGu7u099iKDqiSSLdTp5-szoc",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1_4WZ6yr9sFzNJmWGeipGu7u099iKDqiSSLdTp5-szoc/edit?usp=drivesdk",
          "cachedResultName": "LinkedIn Comments Inbox (Demo)"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 4.5,
      "alwaysOutputData": true
    },
    {
      "id": "766ce8e6-407b-4ecf-a909-2e1d078dedbe",
      "name": "Keep Only New Comments",
      "type": "n8n-nodes-base.code",
      "position": [
        880,
        0
      ],
      "parameters": {
        "jsCode": "\n// Input: the rows already logged in the Google Sheet (may be empty).\n// Keep only parsed comments whose Gmail message id is not in the sheet yet.\nconst logged = new Set(\n  $input.all().map(r => String((r.json || {}).mail_message_id || '')).filter(Boolean)\n);\nconst fresh = [];\nfor (const item of $('Parse Comment Details').all()) {\n  const id = String(item.json.mail_message_id || '');\n  if (!id || logged.has(id)) continue;\n  fresh.push({ json: item.json });\n}\nreturn fresh;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "57495138-2f3f-4ccf-a034-ac1b3666ec6b",
      "name": "Configure Reply Style",
      "type": "n8n-nodes-base.set",
      "position": [
        1104,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "persona-1",
              "name": "persona",
              "type": "string",
              "value": "You are the author of the LinkedIn post. Write a short, personal reply to a comment on your own post. Rules: 1 to 3 sentences, warm and specific to the comment, ask a brief follow-up question when it fits, no hashtags, no emojis, no generic phrases. Reply ONLY with the reply text, without quotation marks."
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "a14c3aed-da1c-4022-b3f9-a562fbb76d21",
      "name": "Draft Reply with Mistral AI",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1328,
        0
      ],
      "parameters": {
        "url": "https://api.mistral.ai/v1/chat/completions",
        "body": "={{ JSON.stringify({model: 'mistral-large-latest', temperature: 0.7, messages: [{role: 'system', content: $json.persona},{role: 'user', content: 'My post (beginning): ' + ($json.post_title || 'unknown') + '\\n\\nComment from ' + ($json.commenter_name || 'someone') + ':\\n' + ($json.comment_text || '')}]}) }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "raw",
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "rawContentType": "application/json",
        "genericAuthType": "httpBearerAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpBearerAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "130e95d7-f980-46b8-9092-7d06945daeba",
      "name": "Append Comment with Draft",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1552,
        0
      ],
      "parameters": {
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 434307311,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1_4WZ6yr9sFzNJmWGeipGu7u099iKDqiSSLdTp5-szoc/edit#gid=434307311",
          "cachedResultName": "Comments"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1_4WZ6yr9sFzNJmWGeipGu7u099iKDqiSSLdTp5-szoc",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1_4WZ6yr9sFzNJmWGeipGu7u099iKDqiSSLdTp5-szoc/edit?usp=drivesdk",
          "cachedResultName": "LinkedIn Comments Inbox (Demo)"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "085a6c37-faa5-46e5-9d36-22562bcfb777",
      "name": "Note -760x-280",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -576,
        -288
      ],
      "parameters": {
        "width": 480,
        "height": 696,
        "content": "## AI reply drafts for your LinkedIn comments\n\n**How it works**\n- LinkedIn's API does not let most apps read comments, so this workflow uses the notification emails LinkedIn sends you instead.\n- Every 30 minutes it searches Gmail for \"commented on your post\" notifications and parses commenter, comment text, post preview and post link.\n- New comments (deduplicated by Gmail message id against your sheet) get a short reply drafted by Mistral AI and are appended to Google Sheets: your human-in-the-loop reply inbox. Nothing is ever posted automatically.\n\n**Setup steps**\n1. Connect **Gmail** (OAuth2) with the account that receives your LinkedIn notifications, and keep those notifications enabled on LinkedIn.\n2. Create a Google Sheet with the columns `received_at`, `commenter_name`, `comment_text`, `post_title`, `post_url`, `mail_message_id`, `ai_draft`, `status` and select spreadsheet + sheet in both Google Sheets nodes.\n3. Create an **HTTP Bearer Auth** credential with your Mistral API key and attach it to the \"Draft Reply with Mistral AI\" node (or swap that node for your favorite LLM).\n4. Edit the persona text in \"Configure Reply Style\" so drafts sound like you.\n\nThe Gmail query covers English and German notification subjects. If LinkedIn emails you in another language, extend the query and the parser regex accordingly."
      },
      "typeVersion": 1
    },
    {
      "id": "1d203634-75c5-4f51-bdb7-e95a5d68fac8",
      "name": "Note -40x-140",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 500,
        "height": 360,
        "content": "## 1. Fetch notifications\nEvery 30 minutes, Gmail is searched for LinkedIn comment notification emails from the last 3 days (English and German subjects)."
      },
      "typeVersion": 1
    },
    {
      "id": "02e4e1bf-9c30-48e0-91b7-45681a5e7192",
      "name": "Note 480x-140",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 840,
        "height": 360,
        "content": "## 2. Parse and dedupe\nExtract commenter, comment, post preview and link from the email, then skip everything already logged in your sheet."
      },
      "typeVersion": 1
    },
    {
      "id": "8e0d4e4d-4a39-4846-af0b-e539945b5b26",
      "name": "Note 1340x-140",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1344,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 460,
        "height": 360,
        "content": "## 3. Draft and log\nMistral writes a short reply suggestion in your voice; the complete row lands in Google Sheets for your review."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "f55a16cc-1fc1-40b4-b9d1-a48c1557f40c",
  "nodeGroups": [],
  "connections": {
    "Every 30 Minutes": {
      "main": [
        [
          {
            "node": "Fetch LinkedIn Notification Emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Logged Comments": {
      "main": [
        [
          {
            "node": "Keep Only New Comments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure Reply Style": {
      "main": [
        [
          {
            "node": "Draft Reply with Mistral AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Comment Details": {
      "main": [
        [
          {
            "node": "Read Logged Comments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Keep Only New Comments": {
      "main": [
        [
          {
            "node": "Configure Reply Style",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Draft Reply with Mistral AI": {
      "main": [
        [
          {
            "node": "Append Comment with Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch LinkedIn Notification Emails": {
      "main": [
        [
          {
            "node": "Parse Comment Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}