{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "0c72eca1-6a9b-4160-953e-2db5fd3a68fd",
      "name": "\ud83d\udce5 Pipeline A \u00b7 Campaign Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        512,
        640
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 448,
        "content": " *(enroll path)*\n`Parse Enroll Command` *(code \u2014 extracts phone/name/company/campaignId, assigns A/B variant randomly)*.`Airtable \u2013 Create Contact` *(appends row to Contacts table)*.`WATI \u2013 Confirm Enrollment` *(confirms to the sales rep)*.`WATI \u2013 Send Welcome to Contact` *(sends first touch message to the new contact)*"
      },
      "typeVersion": 1
    },
    {
      "id": "ccc5979b-edeb-4ffb-9eaa-409e714736e3",
      "name": "\ud83d\udcca Pipeline D \u00b7 Analytics Dashboard",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        1168
      ],
      "parameters": {
        "color": 7,
        "width": 968,
        "height": 384,
        "content": "Analytics Dashboard\n**Flow:**\n*(routed from Command Router when sender types `report`)*.`Airtable \u2013 Read All FollowUps`*(lists all FollowUps records)*.`Airtable \u2013 Read All Engagement`*(lists all Engagement records)*.`Build Analytics Report`*(code \u2014 computes all stats and A/B comparison)*.`WATI \u2013 Send Analytics Report`*(sends full dashboard to the requester)*"
      },
      "typeVersion": 1
    },
    {
      "id": "c959b458-4f23-42a2-8cda-45002cce6fad",
      "name": "Wati Trigger",
      "type": "n8n-nodes-wati.watiTrigger",
      "position": [
        -480,
        1472
      ],
      "parameters": {
        "event": "messageReceived"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "37f201d3-163c-40c6-b8a5-0e669c853bb6",
      "name": "Command Router",
      "type": "n8n-nodes-base.switch",
      "position": [
        -64,
        1440
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Enroll Contact",
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "startsWith"
                    },
                    "leftValue": "={{ $json.text }}",
                    "rightValue": "enroll "
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Analytics Report",
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.text.toLowerCase().trim() }}",
                    "rightValue": "report"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Pause Contact",
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "startsWith"
                    },
                    "leftValue": "={{ $json.text }}",
                    "rightValue": "pause "
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "a6823936-61cb-484f-9865-fe4030a02c0d",
      "name": "Parse Enroll Command",
      "type": "n8n-nodes-base.code",
      "position": [
        544,
        816
      ],
      "parameters": {
        "jsCode": "// Parse: enroll <phone> <name> <company> <campaignId>\n// Example: enroll 919876543210 Priya Sharma Acme Corp CAMP-001\nconst text = ($json.text || '').trim();\nconst senderPhone = $json.waId || $json.from;\nconst senderName  = $json.senderName || 'Rep';\n\nconst parts = text.replace(/^enroll\\s+/i,'').trim().split(' ');\nif (parts.length < 4) {\n  return [{ json: {\n    senderPhone, senderName, valid: false,\n    errMsg: `\u26a0\ufe0f Invalid format. Use:\\n*enroll <phone> <name> <company> <campaignId>*\\nExample: *enroll 919876543210 Priya Acme CAMP-001*`\n  }}];\n}\n\nconst contactPhone = parts[0];\nconst campaignId   = parts[parts.length - 1];\nconst company      = parts.slice(2, parts.length - 1).join(' ') || 'Unknown';\nconst contactName  = parts[1];\n\n// Assign A/B variant randomly\nconst variant = Math.random() < 0.5 ? 'A' : 'B';\n\nconst now = new Date();\nconst tomorrow = new Date(now); tomorrow.setDate(tomorrow.getDate() + 1);\n\nreturn [{ json: {\n  senderPhone, senderName, valid: true,\n  contactPhone, contactName, company, campaignId, variant,\n  enrolledAt:    now.toISOString(),\n  nextFollowUp:  tomorrow.toISOString().split('T')[0],\n  confirmMsg: `\u2705 *Contact Enrolled!*\\n\\n\ud83d\udc64 *${contactName}* (${company})\\n\ud83d\udcde ${contactPhone}\\n\ud83c\udfaf Campaign: ${campaignId}\\n\ud83d\udd00 Variant: ${variant}\\n\\n\ud83d\udcc5 First follow-up: tomorrow`\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "d823f64e-d2a3-47df-85cd-9024a154f3e7",
      "name": "Airtable \u2013 Create Contact",
      "type": "n8n-nodes-base.airtable",
      "position": [
        800,
        816
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appBOslXzqBuBTIv8",
          "cachedResultUrl": "https://airtable.com/appBOslXzqBuBTIv8",
          "cachedResultName": "Campaign Manager"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tbl7ZtUheEOFOvPEL",
          "cachedResultUrl": "https://airtable.com/appBOslXzqBuBTIv8/tbl7ZtUheEOFOvPEL",
          "cachedResultName": "Contacts Table"
        },
        "columns": {
          "value": {
            "phone": "={{ $json.contactPhone }}",
            "status": "Active",
            "company": "={{ $json.company }}",
            "enrolledAt": "={{ $json.enrolledAt }}",
            "nextFollowUp": "={{ $json.nextFollowUp }}",
            "followUpCount": "=0"
          },
          "schema": [
            {
              "id": "Name",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "phone",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "company",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "company",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Campaign Id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Campaign Id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Variant",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Variant",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "followUpCount",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "followUpCount",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "nextFollowUp",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "nextFollowUp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "enrolledAt",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "enrolledAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "1ed3b49c-4e4e-48d2-af08-86c0f85516da",
      "name": "WATI \u2013 Confirm Enrollment",
      "type": "n8n-nodes-wati.wati",
      "position": [
        1040,
        736
      ],
      "parameters": {
        "target": "={{ $('Parse Enroll Command').item.json.senderPhone }}",
        "messageText": "={{ $('Parse Enroll Command').item.json.confirmMsg }}"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "73444670-14cd-4e79-9c26-c8eb0770e60a",
      "name": "WATI \u2013 Send Welcome to Contact",
      "type": "n8n-nodes-wati.wati",
      "position": [
        1056,
        912
      ],
      "parameters": {
        "target": "={{ $('Parse Enroll Command').item.json.contactPhone }}",
        "messageText": "=\ud83d\udc4b Hi *{{ $('Parse Enroll Command').item.json.contactName }}*!\n\nWe wanted to reach out and introduce ourselves. We'll be in touch soon with something relevant to *{{ $('Parse Enroll Command').item.json.company }}*.\n\nFeel free to reply anytime \u2014 we're here to help! \ud83d\ude4c"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "3bb67842-3006-4c5d-a96c-8e1d18857696",
      "name": "Parse Pause Command",
      "type": "n8n-nodes-base.code",
      "position": [
        576,
        1712
      ],
      "parameters": {
        "jsCode": "const text  = ($json.text || '').trim();\nconst phone = text.replace(/^pause\\s+/i,'').trim();\nconst senderPhone = $json.waId || $json.from;\nreturn [{ json: { phone, senderPhone, msg: `\u23f8\ufe0f Contact *${phone}* has been paused.\\nThey will no longer receive follow-ups until resumed.` } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "b71c7a1f-54d3-48c5-8a7f-eac1a84418c4",
      "name": "Airtable \u2013 Pause Contact",
      "type": "n8n-nodes-base.airtable",
      "position": [
        784,
        1712
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appBOslXzqBuBTIv8",
          "cachedResultUrl": "https://airtable.com/appBOslXzqBuBTIv8",
          "cachedResultName": "Campaign Manager"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tbl7ZtUheEOFOvPEL",
          "cachedResultUrl": "https://airtable.com/appBOslXzqBuBTIv8/tbl7ZtUheEOFOvPEL",
          "cachedResultName": "Contacts Table"
        },
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true
            },
            {
              "id": "Name",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "phone",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "company",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "company",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Campaign Id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Campaign Id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Variant",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Variant",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "followUpCount",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "followUpCount",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "nextFollowUp",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "nextFollowUp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "enrolledAt",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "enrolledAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "65c89e37-a59d-4090-91fa-69d0976c7698",
      "name": "WATI \u2013 Confirm Pause",
      "type": "n8n-nodes-wati.wati",
      "position": [
        992,
        1712
      ],
      "parameters": {
        "target": "={{ $json.senderPhone }}",
        "messageText": "={{ $json.msg }}"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "ac02ba19-d670-4539-95ec-819fd9766525",
      "name": "Schedule Trigger \u2013 9AM Daily",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        2576,
        1504
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "d344cbf1-10db-4904-beb8-a7e71e7d3755",
      "name": "Airtable \u2013 Read Active Contacts",
      "type": "n8n-nodes-base.airtable",
      "position": [
        2816,
        1504
      ],
      "parameters": {
        "options": {},
        "resource": "base"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "f9ec16ae-ba7e-45a6-a066-ed29e252a254",
      "name": "Filter Due Follow-ups",
      "type": "n8n-nodes-base.code",
      "position": [
        3056,
        1504
      ],
      "parameters": {
        "jsCode": "// Filter contacts where nextFollowUp <= today AND status = Active\nconst allRows = $input.all();\nconst today   = new Date().toISOString().split('T')[0];\n\nconst due = allRows.filter(r => {\n  const s = (r.json.status || '').toLowerCase();\n  const d = r.json.nextFollowUp || '';\n  return s === 'active' && d <= today;\n});\n\nif (due.length === 0) return [{ json: { message: 'No follow-ups due today', count: 0 } }];\n\nreturn due.map(r => ({ json: {\n  recordId:      r.json.id,\n  contactId:     r.json.id,\n  phone:         r.json.phone,\n  name:          r.json.name,\n  company:       r.json.company,\n  campaignId:    r.json.campaignId,\n  variant:       r.json.variant || 'A',\n  followUpCount: parseInt(r.json.followUpCount || 0),\n  stepNumber:    parseInt(r.json.followUpCount || 0) + 1,\n  lastFollowUp:  r.json.lastFollowUp || null\n}}));"
      },
      "typeVersion": 2
    },
    {
      "id": "fe514b6c-4747-48a3-a1c8-8d2c44461c87",
      "name": "Airtable \u2013 Read Campaign",
      "type": "n8n-nodes-base.airtable",
      "position": [
        3296,
        1504
      ],
      "parameters": {
        "options": {},
        "resource": "base"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "cf1115a6-1b14-4b9b-ae6f-829a48967cc5",
      "name": "OpenAI \u2013 Personalise Follow-up Message",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3536,
        1504
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {},
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "openAiApi"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        },
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "d4108fc2-e81a-4203-a3f9-33974c08824e",
      "name": "Build Follow-up Row",
      "type": "n8n-nodes-base.code",
      "position": [
        3776,
        1504
      ],
      "parameters": {
        "jsCode": "// Build the FollowUps log row + compute next follow-up date\nconst contact  = $('Filter Due Follow-ups').item.json;\nconst msgRaw   = $json?.choices?.[0]?.message?.content || '';\nconst message  = msgRaw.trim();\nconst now      = new Date();\n\n// Compute next follow-up: 3 days from today (adjust per campaign intervalDays if available)\nconst allCampaigns = $('Airtable \u2013 Read Campaign').all ? $('Airtable \u2013 Read Campaign').all() : [];\nconst campaign = allCampaigns.find(r => r.json.campaignId === contact.campaignId);\nconst intervalDays = parseInt(campaign?.json?.intervalDays || 3);\n\nconst nextDate = new Date(now);\nnextDate.setDate(nextDate.getDate() + intervalDays);\n\nconst followUpId = `FU-${contact.phone}-${now.getTime()}`;\n\nreturn [{ json: {\n  followUpId,\n  contactId:    contact.contactId,\n  recordId:     contact.recordId,\n  phone:        contact.phone,\n  name:         contact.name,\n  campaignId:   contact.campaignId,\n  variant:      contact.variant,\n  stepNumber:   contact.stepNumber,\n  messageText:  message,\n  sentAt:       now.toISOString(),\n  nextFollowUp: nextDate.toISOString().split('T')[0],\n  newCount:     contact.followUpCount + 1\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "f04f5e61-95e0-4d93-a8e6-ff1342f89d77",
      "name": "Airtable \u2013 Log Follow-up Sent",
      "type": "n8n-nodes-base.airtable",
      "position": [
        4016,
        1504
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appBOslXzqBuBTIv8",
          "cachedResultUrl": "https://airtable.com/appBOslXzqBuBTIv8",
          "cachedResultName": "Campaign Manager"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblSlSYnAeIG2LVA9",
          "cachedResultUrl": "https://airtable.com/appBOslXzqBuBTIv8/tblSlSYnAeIG2LVA9",
          "cachedResultName": "FollowUps Table"
        },
        "columns": {
          "value": {
            "phone": "={{ $json.phone }}",
            "sentAt": "={{ $json.sentAt }}",
            "variant": "={{ $json.variant }}",
            "contactId": "={{ $json.contactId }}",
            "campaignId": "={{ $json.campaignId }}",
            "stepNumber": "={{ $json.stepNumber }}",
            "messageText": "={{ $json.messageText }}"
          },
          "schema": [
            {
              "id": "followUpId",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "followUpId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "contactId",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "contactId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "phone",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "campaignId",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "campaignId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "variant",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "variant",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "stepNumber",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "stepNumber",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "messageText",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "messageText",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sentAt",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sentAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "eb128198-faaa-4640-8cb1-872504057e8e",
      "name": "Airtable \u2013 Update Contact After Send",
      "type": "n8n-nodes-base.airtable",
      "position": [
        4256,
        1504
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appBOslXzqBuBTIv8",
          "cachedResultUrl": "https://airtable.com/appBOslXzqBuBTIv8",
          "cachedResultName": "Campaign Manager"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tbl7ZtUheEOFOvPEL",
          "cachedResultUrl": "https://airtable.com/appBOslXzqBuBTIv8/tbl7ZtUheEOFOvPEL",
          "cachedResultName": "Contacts Table"
        },
        "columns": {
          "value": {
            "nextFollowUp": "={{ $('Build Follow-up Row').item.json.nextFollowUp }}",
            "followUpCount": "={{ $('Build Follow-up Row').item.json.newCount }}"
          },
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true
            },
            {
              "id": "Name",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "phone",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "company",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "company",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Campaign Id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Campaign Id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Variant",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Variant",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "followUpCount",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "followUpCount",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "nextFollowUp",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "nextFollowUp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "enrolledAt",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "enrolledAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "94ca322c-8279-4335-9778-5b4562c85e75",
      "name": "WATI \u2013 Send Follow-up",
      "type": "n8n-nodes-wati.wati",
      "position": [
        4496,
        1504
      ],
      "parameters": {
        "target": "={{ $('Build Follow-up Row').item.json.phone }}",
        "messageText": "={{ $('Build Follow-up Row').item.json.messageText }}"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "7d1ee1f8-0451-4a5a-9e6c-0532a9532650",
      "name": "Airtable \u2013 Find Contact by Phone",
      "type": "n8n-nodes-base.airtable",
      "position": [
        528,
        2384
      ],
      "parameters": {
        "options": {},
        "resource": "base"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "83d0dfdc-00aa-4d00-a94f-875ecb2d17cd",
      "name": "Find Contact Record",
      "type": "n8n-nodes-base.code",
      "position": [
        784,
        2384
      ],
      "parameters": {
        "jsCode": "// Find the matching contact record for the inbound phone number\nconst inboundPhone = $('Wati Trigger').item.json.waId || $('Wati Trigger').item.json.from;\nconst allRows = $input.all();\nconst match = allRows.find(r => (r.json.phone||'').replace(/\\D/g,'') === inboundPhone.replace(/\\D/g,''));\n\nif (!match) {\n  return [{ json: { found: false, inboundPhone, replyText: $('Wati Trigger').item.json.text } }];\n}\nreturn [{ json: {\n  found:       true,\n  inboundPhone,\n  recordId:    match.json.id,\n  contactId:   match.json.id,\n  name:        match.json.name,\n  company:     match.json.company,\n  campaignId:  match.json.campaignId,\n  variant:     match.json.variant,\n  replyText:   $('Wati Trigger').item.json.text\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "837581e7-94bf-4f5f-8c79-c76a82a11b51",
      "name": "OpenAI \u2013 Detect Reply Intent",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1024,
        2384
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {},
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "openAiApi"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        },
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "e528ff43-3c9d-4964-a67c-8bdb81664558",
      "name": "Process Reply & Build Log",
      "type": "n8n-nodes-base.code",
      "position": [
        1280,
        2384
      ],
      "parameters": {
        "jsCode": "const intent = ($json?.choices?.[0]?.message?.content || 'other').trim().toLowerCase();\nconst contact = $('Find Contact Record').item.json;\nconst now = new Date();\n\nconst intentEmoji = {\n  interested:     '\ud83d\udc40 Interested!',\n  not_interested: '\ud83d\ude4f Not Interested',\n  question:       '\u2753 Has a Question',\n  converted:      '\ud83c\udf89 CONVERTED!',\n  unsubscribe:    '\ud83d\udeab Unsubscribed',\n  other:          '\ud83d\udcac Other'\n}[intent] || '\ud83d\udcac Other';\n\n// Build response message based on intent\nconst responseMap = {\n  interested:     `\ud83d\ude0a Great to hear, *${contact.name}!* We'll be in touch very soon with more details. Is there anything specific you'd like to know?`,\n  not_interested: `No worries at all, *${contact.name}!* We appreciate your time. Feel free to reach out anytime if things change. Wishing you all the best! \ud83d\udc4b`,\n  question:       `Thanks for reaching out, *${contact.name}!* We'll get someone from our team to answer your question as soon as possible. \ud83d\ude4c`,\n  converted:      `\ud83c\udf89 Wonderful, *${contact.name}!* We're so excited to work with you and *${contact.company}*. Our team will contact you shortly to get started!`,\n  unsubscribe:    `Understood, *${contact.name}.* We've removed you from our follow-up list. No more messages from us! Take care. \ud83d\udc4b`,\n  other:          `Thanks for your message, *${contact.name}!* Our team will review and get back to you shortly.`\n};\n\n// New contact status based on intent\nconst newStatus = { converted: 'Converted', unsubscribe: 'Unsubscribed', not_interested: 'Paused' }[intent] || null;\n\nreturn [{ json: {\n  inboundPhone:  contact.inboundPhone,\n  recordId:      contact.recordId,\n  contactId:     contact.contactId,\n  campaignId:    contact.campaignId,\n  variant:       contact.variant,\n  replyText:     contact.replyText,\n  intent,\n  intentEmoji,\n  repliedAt:     now.toISOString(),\n  responseMsg:   responseMap[intent],\n  newStatus,\n  shouldUpdate:  !!newStatus\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "9d4e616a-535e-4df7-9877-8717920c3b32",
      "name": "Airtable \u2013 Log Engagement",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1456,
        2384
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appBOslXzqBuBTIv8",
          "cachedResultUrl": "https://airtable.com/appBOslXzqBuBTIv8",
          "cachedResultName": "Campaign Manager"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblQS5jEqJD0sGk9O",
          "cachedResultUrl": "https://airtable.com/appBOslXzqBuBTIv8/tblQS5jEqJD0sGk9O",
          "cachedResultName": "Engagement Table"
        },
        "columns": {
          "value": {
            "intent": "={{ $json.intent }}",
            "contactId": "={{ $json.contactId }}",
            "repliedAt": "={{ $json.repliedAt }}",
            "replyText": "={{ $json.replyText }}"
          },
          "schema": [
            {
              "id": "engagementId",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "engagementId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "contactId",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "contactId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "intent",
              "type": "boolean",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "intent",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "replyText",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "replyText",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Attachments",
              "type": "array",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Attachments",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "repliedAt",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "repliedAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "64560c52-0b22-4ac1-b8fa-2f45a5c2cf91",
      "name": "Airtable \u2013 Update Contact Status",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1680,
        2384
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appBOslXzqBuBTIv8",
          "cachedResultUrl": "https://airtable.com/appBOslXzqBuBTIv8",
          "cachedResultName": "Campaign Manager"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tbl7ZtUheEOFOvPEL",
          "cachedResultUrl": "https://airtable.com/appBOslXzqBuBTIv8/tbl7ZtUheEOFOvPEL",
          "cachedResultName": "Contacts Table"
        },
        "columns": {
          "value": {
            "status": "={{ $('Process Reply & Build Log').item.json.newStatus }}",
            "followUpCount": 0
          },
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true
            },
            {
              "id": "Name",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "phone",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "company",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "company",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Campaign Id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Campaign Id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Variant",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Variant",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "followUpCount",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "followUpCount",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "nextFollowUp",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "nextFollowUp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "enrolledAt",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "enrolledAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "aa55efda-bd57-4a62-b603-2900e8226366",
      "name": "WATI \u2013 Send Reply Acknowledgement",
      "type": "n8n-nodes-wati.wati",
      "position": [
        1904,
        2384
      ],
      "parameters": {
        "target": "={{ $('Process Reply & Build Log').item.json.inboundPhone }}",
        "messageText": "={{ $('Process Reply & Build Log').item.json.responseMsg }}"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9d83a298-23b8-4254-b6f6-db9a325ce5d7",
      "name": "Airtable \u2013 Read All Follow-ups",
      "type": "n8n-nodes-base.airtable",
      "position": [
        560,
        1328
      ],
      "parameters": {
        "options": {},
        "resource": "base"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "21cf8238-68c0-4c61-b099-d3a4edd91682",
      "name": "Airtable \u2013 Read All Engagement",
      "type": "n8n-nodes-base.airtable",
      "position": [
        736,
        1328
      ],
      "parameters": {
        "options": {},
        "resource": "base"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "f7cca15c-1900-4555-b489-9950fc335f41",
      "name": "Build Analytics Report",
      "type": "n8n-nodes-base.code",
      "position": [
        944,
        1328
      ],
      "parameters": {
        "jsCode": "// Build A/B analytics + campaign dashboard\n// Reads from both FollowUps and Engagement tables\n\nconst requesterPhone = $('Wati Trigger').item.json.waId || $('Wati Trigger').item.json.from;\nconst requesterName  = $('Wati Trigger').item.json.senderName || 'User';\n\nconst followUps  = $('Airtable \u2013 Read All Follow-ups').all().map(r => r.json);\nconst engagement = $('Airtable \u2013 Read All Engagement').all().map(r => r.json);\n\nconst now = new Date();\n\n// \u2500\u2500 Overall stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst totalSent    = followUps.length;\nconst totalReplies = engagement.length;\nconst converted    = engagement.filter(e => e.intent === 'converted').length;\nconst unsubscribed = engagement.filter(e => e.intent === 'unsubscribe').length;\nconst replyRate    = totalSent > 0 ? ((totalReplies / totalSent) * 100).toFixed(1) : '0.0';\nconst convRate     = totalSent > 0 ? ((converted    / totalSent) * 100).toFixed(1) : '0.0';\n\n// \u2500\u2500 A/B breakdown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst sentA = followUps.filter(f => f.variant === 'A').length;\nconst sentB = followUps.filter(f => f.variant === 'B').length;\nconst replA = engagement.filter(e => e.variant === 'A').length;\nconst replB = engagement.filter(e => e.variant === 'B').length;\nconst convA = engagement.filter(e => e.variant === 'A' && e.intent === 'converted').length;\nconst convB = engagement.filter(e => e.variant === 'B' && e.intent === 'converted').length;\nconst rrA   = sentA > 0 ? ((replA / sentA) * 100).toFixed(1) : '0.0';\nconst rrB   = sentB > 0 ? ((replB / sentB) * 100).toFixed(1) : '0.0';\nconst crA   = sentA > 0 ? ((convA / sentA) * 100).toFixed(1) : '0.0';\nconst crB   = sentB > 0 ? ((convB / sentB) * 100).toFixed(1) : '0.0';\nconst winner = parseFloat(crA) >= parseFloat(crB) ? '\ud83c\udfc6 Variant A wins on conversion' : '\ud83c\udfc6 Variant B wins on conversion';\n\n// \u2500\u2500 Per-campaign stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst byCampaign = {};\nfor (const f of followUps) {\n  const c = f.campaignId || 'Unknown';\n  if (!byCampaign[c]) byCampaign[c] = { sent:0, replies:0, conversions:0 };\n  byCampaign[c].sent++;\n}\nfor (const e of engagement) {\n  const c = e.campaignId || 'Unknown';\n  if (!byCampaign[c]) byCampaign[c] = { sent:0, replies:0, conversions:0 };\n  byCampaign[c].replies++;\n  if (e.intent === 'converted') byCampaign[c].conversions++;\n}\n\nconst campLines = Object.entries(byCampaign).map(([id, s]) => {\n  const rr = s.sent > 0 ? ((s.replies/s.sent)*100).toFixed(0) : 0;\n  return `  \ud83d\udcc1 *${id}* \u2014 Sent: ${s.sent} | Replies: ${s.replies} (${rr}%) | Conv: ${s.conversions}`;\n});\n\n// \u2500\u2500 Recent replies \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst intentEmoji = { interested:'\ud83d\udc40',not_interested:'\ud83d\ude4f',question:'\u2753',converted:'\ud83c\udf89',unsubscribe:'\ud83d\udeab',other:'\ud83d\udcac' };\nconst recentReplies = engagement\n  .sort((a,b) => new Date(b.repliedAt) - new Date(a.repliedAt))\n  .slice(0,5)\n  .map(e => {\n    const dt = e.repliedAt ? new Date(e.repliedAt).toLocaleDateString('en-IN',{day:'numeric',month:'short',hour:'2-digit',minute:'2-digit'}) : '';\n    return `  ${intentEmoji[e.intent]||'\ud83d\udcac'} ${e.phone} \u2014 ${e.intent} (${dt})`;\n  });\n\n// \u2500\u2500 Build message \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst lines = [\n  `\ud83d\udcca *Follow-up Campaign Analytics*`,\n  `\ud83d\udc64 ${requesterName} \u00b7 ${now.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})}`,\n  '',\n  `\u2501\u2501 *Overall Performance* \u2501\u2501`,\n  `\ud83d\udce4 Total Sent:      *${totalSent}*`,\n  `\ud83d\udcac Total Replies:   *${totalReplies}* (${replyRate}%)`,\n  `\ud83c\udf89 Conversions:    *${converted}* (${convRate}%)`,\n  `\ud83d\udeab Unsubscribed:   *${unsubscribed}*`,\n  '',\n  `\u2501\u2501 *A/B Test Results* \u2501\u2501`,\n  `\ud83c\udd70\ufe0f Variant A \u2014 Sent: ${sentA} | Reply: ${rrA}% | Conv: ${crA}%`,\n  `\ud83c\udd71\ufe0f Variant B \u2014 Sent: ${sentB} | Reply: ${rrB}% | Conv: ${crB}%`,\n  winner,\n  '',\n  `\u2501\u2501 *By Campaign* \u2501\u2501`,\n  ...campLines,\n  '',\n  `\u2501\u2501 *Recent Replies* \u2501\u2501`,\n  ...recentReplies,\n  '',\n  'Reply *enroll* to add a contact \u2014 *pause* to pause one.'\n];\n\nreturn [{ json: { requesterPhone, report: lines.join('\\n') } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "ffa4b9e0-0301-4d52-b919-23ba426106e9",
      "name": "WATI \u2013 Send Analytics Report",
      "type": "n8n-nodes-wati.wati",
      "position": [
        1200,
        1328
      ],
      "parameters": {
        "target": "={{ $json.requesterPhone }}",
        "messageText": "={{ $json.report }}"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "ba01ef3f-db26-4af1-b0ad-754a96b5d4f6",
      "name": "Pipeline B Guide",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2544,
        1264
      ],
      "parameters": {
        "color": 7,
        "width": 2168,
        "height": 448,
        "content": "### \u23f0 Pipeline B: Follow-up Scheduler\n**Nodes:** `9AM Trigger` \u2192 `Read Contacts` \u2192 `OpenAI Message` \u2192 `Update Contact`.\n\n**Function:**\n1. **Filtering:** Lists all Airtable contacts where `Status=Active` and `NextFollowUp <= Today`.\n2. **AI Writer:** OpenAI generates a custom follow-up message using the contact's name, company, and assigned A/B tone (Formal vs. Friendly).\n3. **CRM Sync:** Increments the follow-up counter and schedules the next touchpoint date."
      },
      "typeVersion": 1
    },
    {
      "id": "93b4c274-97a6-4c09-b68a-b5bbb0177023",
      "name": "Pipeline C Guide",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        512,
        2064
      ],
      "parameters": {
        "color": 7,
        "width": 1592,
        "height": 640,
        "content": "Engagement Tracker\n**Nodes:** `Reply Trigger` \u2192 `Find Record` \u2192 `OpenAI Intent` \u2192 `Log Engagement`.\n\n**Function:**\n1. **Identification:** Matches the sender's WhatsApp number to an existing lead in Airtable.\n2. **AI Intent:** OpenAI classifies the reply (e.g., 'Interested', 'Question', 'Unsubscribe').\n3. **Action:** If the lead is 'Converted' or 'Unsubscribed', the bot automatically pauses further follow-ups to save your reputation."
      },
      "typeVersion": 1
    },
    {
      "id": "f21df589-0287-4a0c-8dac-0d47b427e737",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        512,
        1600
      ],
      "parameters": {
        "color": 7,
        "width": 944,
        "height": 400,
        "content": "Branch: Pause Outreach\nHow it works:Command Parsing: Extracts the target phone number from the WhatsApp command and prepares the confirmation text.\nCRM Update: Updates the lead's status to 'Paused' in Airtable to exclude them from the 9 AM automated follow-up scheduler.\nConfirmation: Sends a WhatsApp alert back to the representative confirming that automated outreach for that lead has stopped."
      },
      "typeVersion": 1
    },
    {
      "id": "94279b21-975b-49dc-8391-dadd8c8dbc28",
      "name": "\ud83d\udccc Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1696,
        -32
      ],
      "parameters": {
        "width": 820,
        "height": 680,
        "content": "\ud83d\udce3 WhatsApp Follow-up Campaign Manager\nWorkflow Purpose\nAutomates personalized outreach via WATI, lead management in Airtable, and AI-driven messaging with OpenAI. Supports A/B testing and real-time performance analytics.\n\n\ud83d\ude80 Core Pipelines\nCampaign Setup: Enrolls contacts and assigns A/B variants via WhatsApp commands.\n\nFollow-up Scheduler: Daily 9 AM trigger for AI-personalized follow-ups based on lead variant.\n\nReply Tracker: Detects reply intent (e.g., Interested, Unsubscribe) and auto-updates CRM status.\n\nAnalytics Dashboard: Generates on-demand A/B stats and conversion reports via the report command.\n\n\ud83d\udd27 Setup Essentials\nRequired Apps\n\nWATI: Triggers and sends WhatsApp communications.\n\nAirtable: Acts as the central lead and campaign CRM.\n\nOpenAI: Handles message personalization and intent classification."
      },
      "typeVersion": 1
    },
    {
      "id": "d3565254-6cb9-4c8a-b442-b5997c6f9c09",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -592,
        992
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 1120,
        "content": "Branch: Command Routing\nNodes: Wati Trigger \u2192 Command Router\n\nHow it works:\n\nCentral Intake: The Wati Trigger listens for all incoming WhatsApp messages and passes the text to the router.\n\nIntent Detection: The Command Router (Switch node) acts as the traffic controller, sorting messages into four distinct paths based on keywords:\n\nEnrollment: Messages starting with enroll .\n\nAnalytics: Messages exactly matching report.\n\nManual Override: Messages starting with pause .\n\nEngagement Tracking: All other replies (via the extra output)."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Wati Trigger": {
      "main": [
        [
          {
            "node": "Command Router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Command Router": {
      "main": [
        [
          {
            "node": "Parse Enroll Command",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Airtable \u2013 Read All Follow-ups",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Parse Pause Command",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Airtable \u2013 Find Contact by Phone",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Follow-up Row": {
      "main": [
        [
          {
            "node": "Airtable \u2013 Log Follow-up Sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Contact Record": {
      "main": [
        [
          {
            "node": "OpenAI \u2013 Detect Reply Intent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Pause Command": {
      "main": [
        [
          {
            "node": "Airtable \u2013 Pause Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Enroll Command": {
      "main": [
        [
          {
            "node": "Airtable \u2013 Create Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Due Follow-ups": {
      "main": [
        [
          {
            "node": "Airtable \u2013 Read Campaign",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Analytics Report": {
      "main": [
        [
          {
            "node": "WATI \u2013 Send Analytics Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Reply & Build Log": {
      "main": [
        [
          {
            "node": "Airtable \u2013 Log Engagement",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable \u2013 Pause Contact": {
      "main": [
        [
          {
            "node": "WATI \u2013 Confirm Pause",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable \u2013 Read Campaign": {
      "main": [
        [
          {
            "node": "OpenAI \u2013 Personalise Follow-up Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable \u2013 Create Contact": {
      "main": [
        [
          {
            "node": "WATI \u2013 Confirm Enrollment",
            "type": "main",
            "index": 0
          },
          {
            "node": "WATI \u2013 Send Welcome to Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable \u2013 Log Engagement": {
      "main": [
        [
          {
            "node": "Airtable \u2013 Update Contact Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI \u2013 Detect Reply Intent": {
      "main": [
        [
          {
            "node": "Process Reply & Build Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger \u2013 9AM Daily": {
      "main": [
        [
          {
            "node": "Airtable \u2013 Read Active Contacts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable \u2013 Log Follow-up Sent": {
      "main": [
        [
          {
            "node": "Airtable \u2013 Update Contact After Send",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable \u2013 Read All Engagement": {
      "main": [
        [
          {
            "node": "Build Analytics Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable \u2013 Read All Follow-ups": {
      "main": [
        [
          {
            "node": "Airtable \u2013 Read All Engagement",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable \u2013 Read Active Contacts": {
      "main": [
        [
          {
            "node": "Filter Due Follow-ups",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable \u2013 Find Contact by Phone": {
      "main": [
        [
          {
            "node": "Find Contact Record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable \u2013 Update Contact Status": {
      "main": [
        [
          {
            "node": "WATI \u2013 Send Reply Acknowledgement",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable \u2013 Update Contact After Send": {
      "main": [
        [
          {
            "node": "WATI \u2013 Send Follow-up",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI \u2013 Personalise Follow-up Message": {
      "main": [
        [
          {
            "node": "Build Follow-up Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}