{
  "id": "zX9gIZbFjzVLWYoP",
  "name": "All In One Unipile Linkedin Auto Reply",
  "tags": [],
  "nodes": [
    {
      "id": "f39626a2-1393-4907-a8be-0926f9ef2965",
      "name": "Watch KB Folder",
      "type": "n8n-nodes-base.googleDriveTrigger",
      "position": [
        80,
        496
      ],
      "parameters": {
        "event": "fileCreated",
        "options": {
          "fileType": "all"
        },
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "triggerOn": "specificFolder",
        "folderToWatch": {
          "__rl": true,
          "mode": "list",
          "value": "1NJtz6JJbL2hLDQdpufKpGdT5ayXIepXb",
          "cachedResultUrl": "https://drive.google.com/drive/folders/1NJtz6JJbL2hLDQdpufKpGdT5ayXIepXb",
          "cachedResultName": "Linked Auto Reply Test Folder"
        }
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "50d9b89a-16d5-4669-97a1-409610f17035",
      "name": "Download File",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        400,
        496
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.id }}"
        },
        "options": {
          "binaryPropertyName": "data"
        },
        "operation": "download"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "fe47d174-f923-4d0b-89d3-d730aa7204c1",
      "name": "Pinecone Insert",
      "type": "@n8n/n8n-nodes-langchain.vectorStorePinecone",
      "position": [
        704,
        496
      ],
      "parameters": {
        "mode": "insert",
        "options": {
          "pineconeNamespace": "company-kb"
        },
        "pineconeIndex": {
          "__rl": true,
          "mode": "list",
          "value": "company-knowledge-base"
        }
      },
      "credentials": {
        "pineconeApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "61b9088b-d5c4-4345-825a-09206eb66556",
      "name": "Default Data Loader",
      "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
      "position": [
        880,
        736
      ],
      "parameters": {
        "options": {
          "metadata": {
            "metadataValues": [
              {
                "name": "filename",
                "value": "={{ $('Watch KB Folder').item.json.name }}"
              },
              {
                "name": "file_id",
                "value": "={{ $('Watch KB Folder').item.json.id }}"
              },
              {
                "name": "ingested_at",
                "value": "={{ $now.toISO() }}"
              }
            ]
          }
        },
        "dataType": "binary",
        "textSplittingMode": "custom"
      },
      "typeVersion": 1.1
    },
    {
      "id": "2e8a0742-08cb-4453-ab21-8fe4192e0ce4",
      "name": "Token Splitter",
      "type": "@n8n/n8n-nodes-langchain.textSplitterTokenSplitter",
      "position": [
        896,
        864
      ],
      "parameters": {
        "chunkSize": 500,
        "chunkOverlap": 50
      },
      "typeVersion": 1
    },
    {
      "id": "bbdb26ba-6cd6-492e-94da-89e938d5f219",
      "name": "Log to KB Log",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1088,
        496
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "appQvM6vBl23Vw171",
          "cachedResultName": "LinkedIn RAG Bot"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "tblhbTmLDXnkxVN0q",
          "cachedResultName": "KB Log"
        },
        "columns": {
          "value": {
            "status": "success",
            "file_id": "={{ $('Watch KB Folder').item.json.id }}",
            "filename": "={{ $('Watch KB Folder').item.json.name }}",
            "timestamp": "={{ $now.toISO() }}",
            "chunks_upserted": "={{ $input.all().length }}"
          },
          "schema": [
            {
              "id": "filename",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "filename",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "file_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "file_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "chunks_upserted",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "chunks_upserted",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "options",
              "display": true,
              "options": [
                {
                  "name": "success",
                  "value": "success"
                },
                {
                  "name": "failed",
                  "value": "failed"
                },
                {
                  "name": "processing",
                  "value": "processing"
                }
              ],
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 2.2
    },
    {
      "id": "96156692-b865-4d5d-b222-245935358536",
      "name": "Embeddings Google Gemini",
      "type": "@n8n/n8n-nodes-langchain.embeddingsGoogleGemini",
      "position": [
        656,
        688
      ],
      "parameters": {
        "modelName": "models/gemini-embedding-2"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8a13ee60-4808-41f5-b7bd-42e5da49a23f",
      "name": "Overview - LinkedIn RAG Bot (Full System)",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -784,
        1200
      ],
      "parameters": {
        "width": 582,
        "height": 920,
        "content": "## LinkedIn Company Page RAG Bot \u2014 Full System (3 Workflows)\n\n### How it works\n1. Workflow 1 watches a Google Drive folder and automatically chunks, embeds, and indexes new documents into a Pinecone knowledge base.\n2. Workflow 2 receives incoming LinkedIn messages via a Unipile webhook, classifies each message's intent, and drafts a reply grounded in the Pinecone knowledge base.\n3. Every drafted reply is saved to Airtable, and either sent automatically or held for human approval depending on the autoSend toggle.\n4. Workflow 3 triggers when a human approves a pending draft in Airtable, sending the approved reply via Unipile and updating its status.\n5. All three workflows log errors to a shared Airtable Errors table for troubleshooting.\n\n### Setup steps\n- [ ] Reconnect the Google Drive credential (Workflow 1)\n- [ ] Reconnect the Unipile credential (all HTTP nodes currently return 401)\n- [ ] Replace the placeholder DSN (api1.unipile.com:13111) with your real Unipile DSN in Workflow 2 and Workflow 3's HTTP Request nodes\n- [ ] Create the Unipile webhook (source: messaging) pointing at Workflow 2's production URL once activated\n- [ ] Set up the Airtable \"Run a script\" automation on the Drafts table (trigger: status = approved) to call Workflow 3's webhook\n- [ ] Add your company name and description to Generate RAG Reply's system prompt (Workflow 2)\n- [ ] Confirm the Pinecone index dimensions match your Gemini embedding model output\n- [ ] Activate all three workflows once the above is complete\n\n### Customization\nFlip the autoSend field on Workflow 2's Config node to true once you trust the AI's replies, to skip human review and send automatically."
      },
      "typeVersion": 1
    },
    {
      "id": "ed27da41-5fe7-4f04-9b23-67b0963bda33",
      "name": "Group - Watch and download source file",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16,
        320
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 372,
        "content": "## Watch and download source file\n\nWatches the Google Drive folder for new or updated documents and downloads each one as binary data."
      },
      "typeVersion": 1
    },
    {
      "id": "b1c2cdaf-57cf-482b-9fec-1b114732900f",
      "name": "Group - Chunk, embed, and log to knowledge base",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        608,
        320
      ],
      "parameters": {
        "color": 7,
        "width": 644,
        "height": 680,
        "content": "## Chunk, embed, and log to knowledge base\n\nSplits the document into 500-token chunks (50 overlap), embeds them with Gemini, upserts into the Pinecone company-knowledge-base index (namespace: company-kb), then logs the result to Airtable KB Log."
      },
      "typeVersion": 1
    },
    {
      "id": "b9b218ee-35ea-46a4-86ef-da76d1d94f68",
      "name": "Airtable Approved Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        80,
        2560
      ],
      "parameters": {
        "path": "airtable-approved-send",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2.1
    },
    {
      "id": "a1764609-5a81-4645-a054-7e10b0a8f425",
      "name": "Extract Payload",
      "type": "n8n-nodes-base.set",
      "position": [
        320,
        2560
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "record_id",
              "name": "record_id",
              "type": "string",
              "value": "={{ $json.body?.record_id ?? $json.record_id }}"
            },
            {
              "id": "chat_id",
              "name": "chat_id",
              "type": "string",
              "value": "={{ $json.body?.chat_id ?? $json.chat_id }}"
            },
            {
              "id": "ai_draft",
              "name": "ai_draft",
              "type": "string",
              "value": "={{ $json.body?.ai_draft ?? $json.ai_draft }}"
            },
            {
              "id": "status",
              "name": "status",
              "type": "string",
              "value": "={{ $json.body?.status ?? $json.status }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "07492d02-eb2d-4647-9ca9-f7ff9805ab73",
      "name": "Valid Approved Payload?",
      "type": "n8n-nodes-base.if",
      "position": [
        544,
        2560
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "4dd291ed-c5b9-4cb4-9398-f23999706c96",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.record_id }}",
              "rightValue": ""
            },
            {
              "id": "0dc80ae1-a1e5-4aca-93b7-cbe0f5261890",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.chat_id }}",
              "rightValue": ""
            },
            {
              "id": "496d785a-2bf8-4ff0-905d-98bf3199fe3f",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.ai_draft }}",
              "rightValue": ""
            },
            {
              "id": "3bebce87-fd1e-46e8-8c5c-4148f2a9f1cd",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.status }}",
              "rightValue": "approved"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "e6d25117-74bc-42f5-8d7d-120e890cc354",
      "name": "Send Approved Reply via Unipile",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        880,
        2544
      ],
      "parameters": {
        "url": "=https://api1.unipile.com:13111/api/v1/chats/{{ $json.chat_id }}/messages",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "multipart-form-data",
        "authentication": "predefinedCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "text",
              "value": "={{ $json.ai_draft }}"
            }
          ]
        },
        "nodeCredentialType": "unipileApi"
      },
      "credentials": {
        "unipileApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "f0454927-90f5-4b39-9426-a26a94222035",
      "name": "Update Draft Status Sent",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1248,
        2432
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "appQvM6vBl23Vw171",
          "cachedResultName": "LinkedIn RAG Bot"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "tblPkjkyXfsHq5aa5",
          "cachedResultName": "Drafts"
        },
        "columns": {
          "value": {
            "id": "={{ $('Extract Payload').item.json.record_id }}",
            "status": "sent",
            "sent_at": "={{ $now.toISO() }}"
          },
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "readOnly": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true
            },
            {
              "id": "sender_name",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "sender_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sender_id",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "sender_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "chat_id",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "chat_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "category",
              "type": "options",
              "display": true,
              "options": [
                {
                  "name": "LEAD",
                  "value": "LEAD"
                },
                {
                  "name": "SUPPORT",
                  "value": "SUPPORT"
                },
                {
                  "name": "PARTNERSHIP",
                  "value": "PARTNERSHIP"
                },
                {
                  "name": "RECRUITMENT",
                  "value": "RECRUITMENT"
                },
                {
                  "name": "OTHER",
                  "value": "OTHER"
                }
              ],
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "category",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "confidence",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "confidence",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "their_message",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "their_message",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ai_draft",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "ai_draft",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "options",
              "display": true,
              "options": [
                {
                  "name": "pending",
                  "value": "pending"
                },
                {
                  "name": "approved",
                  "value": "approved"
                },
                {
                  "name": "sent",
                  "value": "sent"
                },
                {
                  "name": "rejected",
                  "value": "rejected"
                },
                {
                  "name": "send_failed",
                  "value": "send_failed"
                }
              ],
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp",
              "type": "dateTime",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sent_at",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sent_at",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "692e93cd-4f11-41e9-b989-3bbfa8e13073",
      "name": "Mark Send Failed",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1152,
        2704
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "appQvM6vBl23Vw171",
          "cachedResultName": "LinkedIn RAG Bot"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "tblPkjkyXfsHq5aa5",
          "cachedResultName": "Drafts"
        },
        "columns": {
          "value": {
            "id": "={{ $('Extract Payload').item.json.record_id }}",
            "status": "send_failed"
          },
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "readOnly": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true
            },
            {
              "id": "sender_name",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "sender_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sender_id",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "sender_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "chat_id",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "chat_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "category",
              "type": "options",
              "display": true,
              "options": [
                {
                  "name": "LEAD",
                  "value": "LEAD"
                },
                {
                  "name": "SUPPORT",
                  "value": "SUPPORT"
                },
                {
                  "name": "PARTNERSHIP",
                  "value": "PARTNERSHIP"
                },
                {
                  "name": "RECRUITMENT",
                  "value": "RECRUITMENT"
                },
                {
                  "name": "OTHER",
                  "value": "OTHER"
                }
              ],
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "category",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "confidence",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "confidence",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "their_message",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "their_message",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ai_draft",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "ai_draft",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "options",
              "display": true,
              "options": [
                {
                  "name": "pending",
                  "value": "pending"
                },
                {
                  "name": "approved",
                  "value": "approved"
                },
                {
                  "name": "sent",
                  "value": "sent"
                },
                {
                  "name": "rejected",
                  "value": "rejected"
                },
                {
                  "name": "send_failed",
                  "value": "send_failed"
                }
              ],
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp",
              "type": "dateTime",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sent_at",
              "type": "dateTime",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "sent_at",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "d817e686-8529-4e22-80ee-67a286dc8338",
      "name": "Log Send Error",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1328,
        2704
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "appQvM6vBl23Vw171",
          "cachedResultName": "LinkedIn RAG Bot"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "tblNqamVByZhR3TBb",
          "cachedResultName": "Errors"
        },
        "columns": {
          "value": {
            "stage": "Workflow 3 - Send Approved Reply",
            "chat_id": "={{ $('Extract Payload').item.json.chat_id }}",
            "timestamp": "={{ $now.toISO() }}",
            "error_message": "={{ $json.error?.message ?? JSON.stringify($json.error) }}"
          },
          "schema": [
            {
              "id": "stage",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "stage",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "chat_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "chat_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "error_message",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "error_message",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "4d94fa75-0b22-46f3-a05e-bf7874f3f40d",
      "name": "Log Invalid Payload",
      "type": "n8n-nodes-base.airtable",
      "position": [
        880,
        2784
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "appQvM6vBl23Vw171",
          "cachedResultName": "LinkedIn RAG Bot"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "tblNqamVByZhR3TBb",
          "cachedResultName": "Errors"
        },
        "columns": {
          "value": {
            "stage": "Workflow 3 - Validate Payload",
            "chat_id": "={{ $json.chat_id || \"unknown\" }}",
            "timestamp": "={{ $now.toISO() }}",
            "error_message": "={{ \"Invalid or incomplete payload received from Airtable automation: \" + JSON.stringify($json) }}"
          },
          "schema": [
            {
              "id": "stage",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "stage",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "chat_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "chat_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "error_message",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "error_message",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "c4bbec36-9715-4d3d-92d7-14ad46af7b79",
      "name": "Group - Receive and validate approval payload",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32,
        2368
      ],
      "parameters": {
        "color": 7,
        "width": 740,
        "height": 360,
        "content": "## Receive and validate approval payload\n\nReceives the webhook fired by Airtable's automation script when a draft is approved, extracts the payload fields, and validates that all required fields are present with status = approved. Invalid payloads are logged to Airtable (Log Invalid Payload sits just below this cluster)."
      },
      "typeVersion": 1
    },
    {
      "id": "ef64cebd-d643-4a05-bbb8-304d33ca3b20",
      "name": "Group - Send the approved reply via Unipile",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        784,
        2320
      ],
      "parameters": {
        "color": 7,
        "width": 266,
        "height": 408,
        "content": "## Send the approved reply via Unipile\n\nPosts the approved draft text to the LinkedIn conversation via Unipile's send-message endpoint."
      },
      "typeVersion": 1
    },
    {
      "id": "18ad1d4a-ab6d-4c2f-ad5e-37291965b82a",
      "name": "Group - Update or flag draft status",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1072,
        2240
      ],
      "parameters": {
        "color": 7,
        "width": 460,
        "height": 656,
        "content": "## Update or flag draft status\n\nMarks the Airtable draft as sent on success, or as send_failed with an accompanying error log entry on failure."
      },
      "typeVersion": 1
    },
    {
      "id": "21a89f87-9e23-464c-a848-4739df214fb6",
      "name": "Unipile Inbox Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        64,
        1536
      ],
      "parameters": {
        "path": "unipile-inbox",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2.1
    },
    {
      "id": "a3a72865-883b-4a48-86c8-80e23d475aa0",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "position": [
        288,
        1536
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "auto-send",
              "name": "autoSend",
              "type": "boolean",
              "value": false
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "d16c4497-5d12-46f3-9959-99855b9ee211",
      "name": "Extract & Normalize Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        512,
        1536
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "event",
              "name": "event",
              "type": "string",
              "value": "={{ $json.body?.event ?? $json.event }}"
            },
            {
              "id": "feature",
              "name": "feature",
              "type": "string",
              "value": "={{ $json.body?.account_info?.feature ?? $json.account_info?.feature }}"
            },
            {
              "id": "user_id",
              "name": "user_id",
              "type": "string",
              "value": "={{ $json.body?.account_info?.user_id ?? $json.account_info?.user_id }}"
            },
            {
              "id": "chat_id",
              "name": "chat_id",
              "type": "string",
              "value": "={{ $json.body?.chat_id ?? $json.chat_id }}"
            },
            {
              "id": "message_text",
              "name": "message_text",
              "type": "string",
              "value": "={{ $json.body?.message ?? $json.message }}"
            },
            {
              "id": "sender_id",
              "name": "sender_id",
              "type": "string",
              "value": "={{ $json.body?.sender?.attendee_provider_id ?? $json.sender?.attendee_provider_id }}"
            },
            {
              "id": "sender_name",
              "name": "sender_name",
              "type": "string",
              "value": "={{ $json.body?.sender?.attendee_name ?? $json.sender?.attendee_name }}"
            },
            {
              "id": "timestamp",
              "name": "timestamp",
              "type": "string",
              "value": "={{ $json.body?.timestamp ?? $json.timestamp }}"
            },
            {
              "id": "account_id",
              "name": "account_id",
              "type": "string",
              "value": "={{ $json.body?.account_id ?? $json.account_id }}"
            },
            {
              "id": "session_id",
              "name": "session_id",
              "type": "string",
              "value": "={{ $json.body?.sender?.attendee_provider_id ?? $json.sender?.attendee_provider_id }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "2d9c1890-3453-4868-9c28-6f5e9a8d2d54",
      "name": "Valid Inbound Message?",
      "type": "n8n-nodes-base.if",
      "position": [
        800,
        1536
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "ade8393e-f5ee-44a9-b83e-77a0a08603b8",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.event }}",
              "rightValue": "message_received"
            },
            {
              "id": "53602d50-e6d4-4f76-8adb-38b8edddaa60",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.feature }}",
              "rightValue": "organization"
            },
            {
              "id": "55142b00-4cf2-479d-8f7c-c7ff198e3f00",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.sender_id }}",
              "rightValue": "={{ $json.user_id }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "b73fac2b-4258-4cf3-8726-048aa805f8ab",
      "name": "Fetch Thread History",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        1024,
        1536
      ],
      "parameters": {
        "url": "=https://api1.unipile.com:13111/api/v1/chats/{{ $json.chat_id }}/messages",
        "options": {},
        "sendQuery": true,
        "authentication": "predefinedCredentialType",
        "queryParameters": {
          "parameters": [
            {
              "name": "limit",
              "value": "6"
            }
          ]
        },
        "nodeCredentialType": "unipileApi"
      },
      "credentials": {
        "unipileApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "96b6a02a-99ca-45c9-990e-f84aaa396dfc",
      "name": "Format Thread Context",
      "type": "n8n-nodes-base.code",
      "position": [
        1248,
        1440
      ],
      "parameters": {
        "jsCode": "const raw = $input.first().json.items || $input.first().json || [];\nconst newestFirst = Array.isArray(raw) ? raw : [];\n// Unipile returns messages newest-first; reverse to chronological order (oldest -> newest)\nconst chronological = [...newestFirst].reverse();\n// Drop the newest message from the transcript since it's already surfaced separately\n// as \"message_text\" (the latest inbound message) - avoids duplicating it in the prompt\nconst priorMessages = chronological.slice(0, -1);\nconst lines = priorMessages.map((m) => {\n  const who = m.is_sender ? 'Page' : 'Them';\n  return who + ': ' + (m.text || '');\n});\nconst context = lines.length > 0 ? lines.join('\\n') : '(No prior messages - this is the start of the conversation.)';\nconst extract = $('Extract & Normalize Fields').first().json;\nreturn [{\n  json: {\n    context: context,\n    message_text: extract.message_text,\n    sender_name: extract.sender_name,\n    chat_id: extract.chat_id\n  },\n  pairedItem: { item: 0 }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "0ccbd569-dfba-4abd-9e87-8b30dac3d2c8",
      "name": "Claude via OpenRouter",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        1472,
        1664
      ],
      "parameters": {
        "model": "openrouter/free",
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "edef26aa-09d2-4553-920c-954586aa5701",
      "name": "Conversation Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "position": [
        1600,
        1664
      ],
      "parameters": {
        "sessionKey": "={{ $('Extract & Normalize Fields').first().json.session_id }}",
        "sessionIdType": "customKey",
        "contextWindowLength": 4
      },
      "typeVersion": 1.4
    },
    {
      "id": "7a901bb5-62bd-4603-b084-a71ca598aa13",
      "name": "Classification Schema",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        1728,
        1664
      ],
      "parameters": {
        "jsonSchemaExample": "{ \"category\": \"LEAD\", \"confidence\": 0.95, \"reason\": \"Asked about pricing\" }"
      },
      "typeVersion": 1.3
    },
    {
      "id": "e129990c-ba17-4448-aa9b-fcf197869fca",
      "name": "Is Spam?",
      "type": "n8n-nodes-base.if",
      "position": [
        1872,
        1440
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e43bc4b2-c9b9-4cbd-a557-4405e2b2f238",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.output.category }}",
              "rightValue": "SPAM"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "cd32dbc0-6d58-418e-9c9a-647ee9153feb",
      "name": "Log Spam",
      "type": "n8n-nodes-base.airtable",
      "position": [
        2240,
        1232
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "appQvM6vBl23Vw171",
          "cachedResultName": "LinkedIn RAG Bot"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "tblv2pj2nzHVDgGBP",
          "cachedResultName": "Spam"
        },
        "columns": {
          "value": {
            "reason": "={{ $json.output.reason }}",
            "sender_id": "={{ $('Extract & Normalize Fields').first().json.sender_id }}",
            "timestamp": "={{ $now.toISO() }}",
            "sender_name": "={{ $('Format Thread Context').first().json.sender_name }}",
            "their_message": "={{ $('Format Thread Context').first().json.message_text }}"
          },
          "schema": [
            {
              "id": "sender_name",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sender_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sender_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sender_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "their_message",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "their_message",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "reason",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "reason",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "0a65edaf-6401-4ef5-aa94-3e7959dc1e61",
      "name": "Generate RAG Reply",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2176,
        1632
      ],
      "parameters": {
        "text": "=Conversation so far:\n{{ $('Format Thread Context').item.json.context }}\n\nLatest message from {{ $('Format Thread Context').item.json.sender_name }}: {{ $('Format Thread Context').item.json.message_text }}",
        "options": {
          "systemMessage": "=You are the LinkedIn page assistant for our company. Search the knowledge base tool before every reply. Never invent pricing, features, or facts. If unsure, say a human will follow up shortly. Reply in the language the sender used. Keep replies to 3-5 sentences, LinkedIn inbox style. Message category: {{ $('Classify Message').item.json.output.category }} ({{ $('Classify Message').item.json.output.reason }})"
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "d8e4f5a6-c279-4fa3-9f15-bab0d15ae4c1",
      "name": "Knowledge Base",
      "type": "@n8n/n8n-nodes-langchain.vectorStorePinecone",
      "position": [
        2208,
        1808
      ],
      "parameters": {
        "mode": "retrieve-as-tool",
        "options": {
          "pineconeNamespace": "company-kb"
        },
        "pineconeIndex": {
          "__rl": true,
          "mode": "list",
          "value": "company-knowledge-base",
          "cachedResultName": "company-knowledge-base"
        },
        "toolDescription": "Search the company knowledge base for facts about products, pricing, features, and policies before answering a customer question.",
        "includeDocumentMetadata": false
      },
      "credentials": {
        "pineconeApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "bb7b963c-f62e-41c3-8193-2aa37a2dd6be",
      "name": "Query Embeddings (Gemini)",
      "type": "@n8n/n8n-nodes-langchain.embeddingsGoogleGemini",
      "position": [
        2288,
        1936
      ],
      "parameters": {
        "modelName": "models/gemini-embedding-2"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "10060da7-9cbb-4f16-aded-22d1f6c0d046",
      "name": "Save Draft to Airtable",
      "type": "n8n-nodes-base.airtable",
      "position": [
        2608,
        1632
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "appQvM6vBl23Vw171",
          "cachedResultName": "LinkedIn RAG Bot"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "tblPkjkyXfsHq5aa5",
          "cachedResultName": "Drafts"
        },
        "columns": {
          "value": {
            "status": "pending",
            "chat_id": "={{ $('Extract & Normalize Fields').first().json.chat_id }}",
            "ai_draft": "={{ $json.output }}",
            "category": "={{ $('Classify Message').first().json.output.category }}",
            "sender_id": "={{ $('Extract & Normalize Fields').first().json.sender_id }}",
            "timestamp": "={{ $now.toISO() }}",
            "confidence": "={{ $('Classify Message').first().json.output.confidence }}",
            "sender_name": "={{ $('Format Thread Context').first().json.sender_name }}",
            "their_message": "={{ $('Format Thread Context').first().json.message_text }}"
          },
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true
            },
            {
              "id": "sender_name",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sender_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sender_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sender_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "chat_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "chat_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "category",
              "type": "options",
              "display": true,
              "options": [
                {
                  "name": "LEAD",
                  "value": "LEAD"
                },
                {
                  "name": "SUPPORT",
                  "value": "SUPPORT"
                },
                {
                  "name": "PARTNERSHIP",
                  "value": "PARTNERSHIP"
                },
                {
                  "name": "RECRUITMENT",
                  "value": "RECRUITMENT"
                },
                {
                  "name": "OTHER",
                  "value": "OTHER"
                }
              ],
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "category",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "confidence",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "confidence",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "their_message",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "their_message",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ai_draft",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "ai_draft",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "options",
              "display": true,
              "options": [
                {
                  "name": "pending",
                  "value": "pending"
                },
                {
                  "name": "approved",
                  "value": "approved"
                },
                {
                  "name": "sent",
                  "value": "sent"
                },
                {
                  "name": "rejected",
                  "value": "rejected"
                },
                {
                  "name": "send_failed",
                  "value": "send_failed"
                }
              ],
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sent_at",
              "type": "dateTime",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "sent_at",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "chat_id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "upsert"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "06ecb5c2-a85f-4bf8-8819-7d21cc1b20dc",
      "name": "Auto-Send Enabled?",
      "type": "n8n-nodes-base.if",
      "position": [
        2848,
        1632
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $('Config').item.json.autoSend }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "c574e35a-5dd8-46bd-b55c-f77b92a9db2d",
      "name": "Send Reply via Unipile",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        3152,
        1648
      ],
      "parameters": {
        "url": "=https://api1.unipile.com:13111/api/v1/chats/{{ $('Extract & Normalize Fields').item.json.chat_id }}/messages",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "multipart-form-data",
        "authentication": "predefinedCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "text",
              "value": "={{ $('Generate RAG Reply').item.json.output }}"
            }
          ]
        },
        "nodeCredentialType": "unipileApi"
      },
      "credentials": {
        "unipileApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "38f7b1c3-2b18-41ec-ae22-1f67363f6637",
      "name": "Log Fetch Error",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1248,
        1632
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "appQvM6vBl23Vw171",
          "cachedResultName": "LinkedIn RAG Bot"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "tblNqamVByZhR3TBb",
          "cachedResultName": "Errors"
        },
        "columns": {
          "value": {
            "stage": "Fetch Thread History",
            "chat_id": "={{ $('Extract & Normalize Fields').item.json.chat_id }}",
            "timestamp": "={{ $now.toISO() }}",
            "error_message": "={{ $json.error?.message ?? JSON.stringify($json.error) }}"
          },
          "schema": [
            {
              "id": "stage",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "stage",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "chat_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "chat_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "error_message",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "error_message",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "9e94f1be-d685-482d-90a9-8a716b829853",
      "name": "Classify Message",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1536,
        1440
      ],
      "parameters": {
        "text": "=Conversation so far:\n{{ $json.context }}\n\nLatest message from {{ $json.sender_name }}: {{ $json.message_text }}",
        "options": {
          "systemMessage": "Classify the latest LinkedIn message into exactly one category: LEAD (product, service, pricing, or demo interest), SUPPORT (existing customer needing help), PARTNERSHIP (business collaboration proposal), RECRUITMENT (job inquiry or recruiter outreach), SPAM (irrelevant, mass outreach, or bot), OTHER (anything else). Return the category, a confidence score between 0 and 1, and a short reason. Respond with JSON only."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "0a0f3093-5f2c-42b8-8f22-8c2a4fd24615",
      "name": "Group - Receive webhook and normalize payload",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        1344
      ],
      "parameters": {
        "color": 7,
        "width": 724,
        "height": 392,
        "content": "## Receive webhook and normalize payload\n\nReceives the incoming LinkedIn message from Unipile, responds immediately, sets the auto-send config default, and extracts/normalizes the relevant fields from the webhook payload."
      },
      "typeVersion": 1
    },
    {
      "id": "2e753dc0-4bbd-4f2b-8d41-c96024d57bf4",
      "name": "Group - Validate sender and fetch conversation history",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        752,
        1280
      ],
      "parameters": {
        "color": 7,
        "width": 680,
        "height": 524,
        "content": "## Validate sender and fetch conversation history\n\nFilters for genuine inbound company-page messages (not spam, not self-sent), then fetches the last 6 messages and formats them into a chronological transcript. Fetch errors are logged to Airtable."
      },
      "typeVersion": 1
    },
    {
      "id": "cde31aaf-1fb2-4e67-bac6-91fe8e843c21",
      "name": "Group - Classify message and check for spam",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1456,
        1200
      ],
      "parameters": {
        "color": 7,
        "width": 632,
        "height": 680,
        "content": "## Classify message and check for spam\n\nClassifies the message's intent (LEAD/SUPPORT/PARTNERSHIP/RECRUITMENT/SPAM/OTHER) using Claude via OpenRouter with shared session memory, then routes spam messages to a dedicated Airtable log (Log Spam sits just to the right, sharing this cluster)."
      },
      "typeVersion": 1
    },
    {
      "id": "89718562-5363-4128-bd23-5302da68ace8",
      "name": "Group - Generate grounded reply via Pinecone",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2112,
        1392
      ],
      "parameters": {
        "color": 7,
        "width": 420,
        "height": 696,
        "content": "## Generate grounded reply via Pinecone\n\nGenerates the reply using the same shared Claude model and session memory from classification, grounding every answer in the Pinecone knowledge base via a retrieval tool with matching Gemini embeddings."
      },
      "typeVersion": 1
    },
    {
      "id": "9ab0fd55-2be4-40a5-8bdf-b0171642aa51",
      "name": "Group - Save draft and check auto-send toggle",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2544,
        1408
      ],
      "parameters": {
        "color": 7,
        "width": 460,
        "height": 412,
        "content": "## Save draft and check auto-send toggle\n\nUpserts the AI draft into Airtable Drafts (matched by chat_id, so a conversation updates in place instead of duplicating) and checks the workflow's auto-send configuration."
      },
      "typeVersion": 1
    },
    {
      "id": "880c8583-2db8-4e43-bac8-e06b521b01ef",
      "name": "Group - Send approved reply and log outcome",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3024,
        1408
      ],
      "parameters": {
        "color": 7,
        "width": 558,
        "height": 484,
        "content": "## Send approved reply and log outcome\n\nSends the reply via Unipile when auto-send is on, then marks the draft sent or logs a send error to Airtable."
      },
      "typeVersion": 1
    },
    {
      "id": "7da366eb-905b-4374-a580-b269dc2a5b9d",
      "name": "Update Draft Status Sent1",
      "type": "n8n-nodes-base.airtable",
      "position": [
        3392,
        1536
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "appQvM6vBl23Vw171",
          "cachedResultName": "LinkedIn RAG Bot"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "tblPkjkyXfsHq5aa5",
          "cachedResultName": "Drafts"
        },
        "columns": {
          "value": {
            "status": "sent",
            "chat_id": "={{ $('Extract & Normalize Fields').item.json.chat_id }}",
            "sent_at": "={{ $now.toISO() }}"
          },
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true
            },
            {
              "id": "sender_name",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "sender_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sender_id",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "sender_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "chat_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "chat_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "category",
              "type": "options",
              "display": true,
              "options": [
                {
                  "name": "LEAD",
                  "value": "LEAD"
                },
                {
                  "name": "SUPPORT",
                  "value": "SUPPORT"
                },
                {
                  "name": "PARTNERSHIP",
                  "value": "PARTNERSHIP"
                },
                {
                  "name": "RECRUITMENT",
                  "value": "RECRUITMENT"
                },
                {
                  "name": "OTHER",
                  "value": "OTHER"
                }
              ],
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "category",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "confidence",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "confidence",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "their_message",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "their_message",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ai_draft",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "ai_draft",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "options",
              "display": true,
              "options": [
                {
                  "name": "pending",
                  "value": "pending"
                },
                {
                  "name": "approved",
                  "value": "approved"
                },
                {
                  "name": "sent",
                  "value": "sent"
                },
                {
                  "name": "rejected",
                  "value": "rejected"
                },
                {
                  "name": "send_failed",
                  "value": "send_failed"
                }
              ],
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp",
              "type": "dateTime",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sent_at",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sent_at",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "chat_id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "7ca74a45-9d2b-4075-8429-611269cafe6c",
      "name": "Log Send Error1",
      "type": "n8n-nodes-base.airtable",
      "position": [
        3392,
        1728
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "appQvM6vBl23Vw171",
          "cachedResultName": "LinkedIn RAG Bot"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "tblNqamVByZhR3TBb",
          "cachedResultName": "Errors"
        },
        "columns": {
          "value": {
            "stage": "Send Reply",
            "chat_id": "={{ $('Extract & Normalize Fields').first().json.chat_id }}",
            "timestamp": "={{ $now.toISO() }}",
            "error_message": "={{ $json.error?.message ?? JSON.stringify($json.error) }}"
          },
          "schema": [
            {
              "id": "stage",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "stage",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "chat_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "chat_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "error_message",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "error_message",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "aa8ad3d3-a042-4963-8e3c-fda5d903aed6",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -32,
        208
      ],
      "parameters": {
        "color": 2,
        "width": 1328,
        "height": 864,
        "content": "# Workflow 1 \u2014 Knowledge Base Ingestion Pipeline"
      },
      "typeVersion": 1
    },
    {
      "id": "ee19dd00-9dc9-486c-a5f7-24adae0e0956",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        1104
      ],
      "parameters": {
        "color": 2,
        "width": 3696,
        "height": 1040,
        "content": "# Workflow 2 \u2014 Company Page Inbox Monitor + RAG Bot"
      },
      "typeVersion": 1
    },
    {
      "id": "4a309491-b546-4979-a7f0-9aa0c0f29f93",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -32,
        2192
      ],
      "parameters": {
        "color": 2,
        "width": 1616,
        "height": 768,
        "content": "# Workflow 3 - Human-Approved Send"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "caf9b901-0c30-4eee-9258-17952580828a",
  "nodeGroups": [],
  "connections": {
    "Config": {
      "main": [
        [
          {
            "node": "Extract & Normalize Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Spam?": {
      "main": [
        [
          {
            "node": "Log Spam",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Generate RAG Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download File": {
      "main": [
        [
          {
            "node": "Pinecone Insert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Knowledge Base": {
      "ai_tool": [
        [
          {
            "node": "Generate RAG Reply",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Token Splitter": {
      "ai_textSplitter": [
        [
          {
            "node": "Default Data Loader",
            "type": "ai_textSplitter",
            "index": 0
          }
        ]
      ]
    },
    "Extract Payload": {
      "main": [
        [
          {
            "node": "Valid Approved Payload?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pinecone Insert": {
      "main": [
        [
          {
            "node": "Log to KB Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Watch KB Folder": {
      "main": [
        [
          {
            "node": "Download File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Message": {
      "main": [
        [
          {
            "node": "Is Spam?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Send Failed": {
      "main": [
        [
          {
            "node": "Log Send Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Auto-Send Enabled?": {
      "main": [
        [
          {
            "node": "Send Reply via Unipile",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate RAG Reply": {
      "main": [
        [
          {
            "node": "Save Draft to Airtable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Conversation Memory": {
      "ai_memory": [
        [
          {
            "node": "Generate RAG Reply",
            "type": "ai_memory",
            "index": 0
          },
          {
            "node": "Classify Message",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Default Data Loader": {
      "ai_document": [
        [
          {
            "node": "Pinecone Insert",
            "type": "ai_document",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Thread History": {
      "main": [
        [
          {
            "node": "Format Thread Context",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Fetch Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classification Schema": {
      "ai_outputParser": [
        [
          {
            "node": "Classify Message",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Claude via OpenRouter": {
      "ai_languageModel": [
        [
          {
            "node": "Generate RAG Reply",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Classify Message",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Format Thread Context": {
      "main": [
        [
          {
            "node": "Classify Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Unipile Inbox Webhook": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Draft to Airtable": {
      "main": [
        [
          {
            "node": "Auto-Send Enabled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Reply via Unipile": {
      "main": [
        [
          {
            "node": "Update Draft Status Sent1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Send Error1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Valid Inbound Message?": {
      "main": [
        [
          {
            "node": "Fetch Thread History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Valid Approved Payload?": {
      "main": [
        [
          {
            "node": "Send Approved Reply via Unipile",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Invalid Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Embeddings Google Gemini": {
      "ai_embedding": [
        [
          {
            "node": "Pinecone Insert",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "Airtable Approved Webhook": {
      "main": [
        [
          {
            "node": "Extract Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Query Embeddings (Gemini)": {
      "ai_embedding": [
        [
          {
            "node": "Knowledge Base",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "Extract & Normalize Fields": {
      "main": [
        [
          {
            "node": "Valid Inbound Message?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Approved Reply via Unipile": {
      "main": [
        [
          {
            "node": "Update Draft Status Sent",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mark Send Failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}