This workflow follows the HTTP Request → OpenAI recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"updatedAt": "2026-02-15T05:51:28.264Z",
"createdAt": "2026-02-14T13:38:37.229Z",
"id": "NpLSu1I9MQeowXaP",
"name": "BnB Outreach Replier (Airtable)",
"description": null,
"active": false,
"isArchived": false,
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "bnb_autoresponse",
"responseMode": "responseNode",
"options": {}
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
8832,
22320
]
},
{
"parameters": {
"jsCode": "const raw = $input.all()[0].json;\nconst query = raw.query || {};\nconst mode = query['hub.mode'];\n\n// Verification request (GET from Meta)\nif (mode === 'subscribe') {\n if (query['hub.verify_token'] === 'bnb_verify_123') {\n return [{ json: { challenge: query['hub.challenge'], __isVerification: true } }];\n }\n throw new Error('Verification failed: token mismatch');\n}\n\n// Message webhook (POST from Meta) - pass through\nreturn [{ json: { ...raw, __isVerification: false } }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
9072,
22320
],
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Code in JavaScript"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond-verify",
"leftValue": "={{ String($json.__isVerification) }}",
"rightValue": "true",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Is Verification?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
9312,
22320
]
},
{
"parameters": {
"respondWith": "text",
"responseBody": "={{ $json.challenge }}",
"options": {
"responseCode": 200
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
9552,
22080
],
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Respond to Webhook"
},
{
"parameters": {
"respondWith": "text",
"responseBody": "EVENT_RECEIVED",
"options": {
"responseCode": 200
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
9552,
22320
],
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Respond OK"
},
{
"parameters": {
"jsCode": "// Transform Meta webhook payloads (Instagram + Messenger) into our standard format\nconst raw = $input.all()[0].json;\n\n// --- SIMPLE FORMAT CHECKS (manual test via curl) ---\nif (raw.body && raw.body.message && raw.body.sender_name) {\n return [{ json: raw }];\n}\nif (raw.message && raw.sender_name) {\n return [{ json: { body: raw } }];\n}\n\n// --- META WEBHOOK FORMAT ---\nconst payload = (raw.entry ? raw : null) || (raw.body && raw.body.entry ? raw.body : null);\nif (!payload || !payload.entry || !payload.entry[0]) {\n return [{ json: { body: { message: JSON.stringify(raw), sender_name: 'unknown', sender_id: 'unknown', platform: 'unknown' } } }];\n}\n\nconst entry = payload.entry[0];\nconst messaging = entry.messaging && entry.messaging[0];\n\n// *** CRITICAL FIX: IGNORE ECHOES ***\n// If the message contains 'is_echo', it was sent by us. Ignore it.\nif (!messaging || !messaging.message || messaging.message.is_echo) {\n return [];\n}\n\nlet platform = 'unknown';\nif (payload.object === 'instagram') {\n platform = 'instagram';\n} else if (payload.object === 'page') {\n platform = 'messenger';\n}\n\nreturn [{\n json: {\n body: {\n message: messaging.message.text || '',\n sender_name: messaging.sender.id,\n sender_id: messaging.sender.id,\n platform: platform,\n message_id: messaging.message.mid || '',\n timestamp: messaging.timestamp || Date.now()\n }\n }\n}];"
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Transform Webhook Payload",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
9792,
22320
]
},
{
"parameters": {
"jsCode": "const items = $input.all();\nfor (const item of items) {\n const body = item.json.body || {};\n item.json.prospect_id = body.prospect_id || `${(body.platform || 'unknown')}_${(body.sender_name || 'unknown').toLowerCase().replace(/\\\\s+/g, '_')}`;\n}\nreturn items;"
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Set Prospect Context",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
10032,
22320
]
},
{
"parameters": {
"jsCode": "const AT_PAT = '{{YOUR_AIRTABLE_API_KEY}}';\nconst AT_API = 'https://api.airtable.com/v0/appGx62c5AqsgnOVu';\nconst AT_H = {'Authorization': `Bearer ${AT_PAT}`, 'Content-Type': 'application/json'};\nconst LEADS = 'tbl15eFzJ96VJmRZX';\nconst prospect = $input.all()[0].json;\ntry {\n const data = await this.helpers.httpRequest({\n method: 'GET',\n url: `${AT_API}/${LEADS}?filterByFormula=${encodeURIComponent(`{prospect_id}='${prospect.prospect_id}'`)}`,\n headers: AT_H\n });\n if (data.records?.some(r => r.fields?.['Conversation Mode'] === 'Human Only')) return [];\n} catch(e) {}\nreturn [{ json: prospect }];"
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Check Lead & Process?",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
10272,
22320
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "const AT_PAT = '{{YOUR_AIRTABLE_API_KEY}}';\nconst AT_API = 'https://api.airtable.com/v0/appGx62c5AqsgnOVu';\nconst AT_H = {'Authorization': `Bearer ${AT_PAT}`, 'Content-Type': 'application/json'};\nconst LEADS = 'tbl15eFzJ96VJmRZX';\nconst prospect = $input.all()[0].json;\nconst body = prospect.body || {};\nconst now = new Date().toISOString();\ntry {\n const leadData = await this.helpers.httpRequest({\n method: 'PATCH',\n url: `${AT_API}/${LEADS}`,\n headers: AT_H,\n body: {\n typecast: true,\n performUpsert: { fieldsToMergeOn: ['prospect_id'] },\n records: [{ fields: {\n prospect_id: prospect.prospect_id,\n 'Name': body.sender_name || '',\n 'Platform': body.platform === 'instagram' ? 'Instagram' : 'Facebook Messenger',\n 'Lead Status': 'Contacted',\n 'Pipeline Stage': 'Outreach Sent',\n 'Last Message Date': now,\n 'Conversation Mode': 'AI Auto',\n 'Assigned To': 'AI'\n }}]\n }\n });\n return [{ json: { ...prospect, lead_record_id: leadData.records?.[0]?.id } }];\n} catch(e) {\n return [{ json: { ...prospect, upsert_error: e.message } }];\n}"
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Upsert Lead on Reply",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
10512,
22320
],
"continueOnFail": true
},
{
"parameters": {
"url": "=https://graph.facebook.com/v21.0/me/conversations?user_id={{ $('Set Prospect Context').item.json.body.sender_id }}&access_token=EAASVwxe8SZCsBQnhAZArnCM2yEj9sfBW5PtuGFe7EQHtinl6FQPcUWPWRCSAfOhuNkLMtIDvx2ZA25GcLQ4dbZBUoW3vuJSC1G2eQ8hmImntldibfRtpLxmdTBY3ZCkuhgvsk3PYyZBGh5KGJZA7bwAiStT5Ehe8zMz8Dl05m1R44TJUlClQNSxVRUtkqfhh4EwNSyV7t5QZC6VJIfcaRTJ5jrgFVxhXwJJ3XlFycAZDZD{{ $('Set Prospect Context').item.json.body.platform === 'instagram' ? '&platform=instagram' : '' }}",
"options": {}
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Fetch Conversation",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
10752,
22320
],
"continueOnFail": true
},
{
"parameters": {
"url": "=https://graph.facebook.com/v21.0/{{ $json.data ? $json.data[0].id : 'skip' }}/messages?fields=message,from,created_time&limit=10&access_token=EAASVwxe8SZCsBQnhAZArnCM2yEj9sfBW5PtuGFe7EQHtinl6FQPcUWPWRCSAfOhuNkLMtIDvx2ZA25GcLQ4dbZBUoW3vuJSC1G2eQ8hmImntldibfRtpLxmdTBY3ZCkuhgvsk3PYyZBGh5KGJZA7bwAiStT5Ehe8zMz8Dl05m1R44TJUlClQNSxVRUtkqfhh4EwNSyV7t5QZC6VJIfcaRTJ5jrgFVxhXwJJ3XlFycAZDZD",
"options": {}
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Fetch Messages",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
10992,
22320
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "const fetchData = $input.all()[0].json;\nconst originalData = $('Set Prospect Context').item.json;\nconst businessId = 'YOUR_BUSINESS_ACCOUNT_ID';\n\nlet history = 'No previous messages (first interaction)';\ntry {\n const messages = (fetchData.data || []).reverse();\n if (messages.length > 0) {\n history = messages.map(m => {\n const isMe = m.from && m.from.id === businessId;\n return (isMe ? 'You' : 'Prospect') + ': ' + m.message;\n }).join('\\n');\n }\n} catch (e) {\n history = 'Could not load history';\n}\n\nreturn [{ json: { ...originalData, conversation_history: history } }];"
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Build Context",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
11232,
22320
]
},
{
"parameters": {
"values": {
"string": [
{
"name": "business_name",
"value": "Acme Corp"
},
{
"name": "offer_description",
"value": "We help agencies scale with AI"
},
{
"name": "offer_outcomes",
"value": "10 qualified calls/month"
},
{
"name": "prospect_role",
"value": "CEO"
}
]
},
"options": {}
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Lead Enrichment (Mock)",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [
11472,
22320
]
},
{
"parameters": {
"resource": "chat",
"model": "gpt-4.1",
"prompt": {
"messages": [
{
"role": "system",
"content": "You are \"Aura,\" the AI Growth Strategist for BuildnBloom. You are speaking to NDIS Providers.\n\nYOUR OBJECTIVE:\nYou are NOT here to chit-chat. You are here to move them through the \"NDIS Capacity Unlock Blueprint\" framework.\nYour goal is to get them to acknowledge the \"Revenue Leak\" ($118k/yr loss) so they feel the need for our automation.\n\nCORE FRAMEWORK (The Blueprint):\n1. THE PROBLEM: NDIS providers lose ~$118k/yr (approx 3 participants) due to slow intake responses.\n2. THE SOLUTION: The \"Agentic AI Stack\" (n8n + AI Agents).\n3. THE MECHANISM:\n - \"Zero-Latency\" Response Agent (replies in <60s).\n - \"Smart Intake\" Coordinator (qualifies leads instantly).\n - \"Automated Agreement\" Fast-Track (contracts in 7 mins, not 7 days).\n\nTONE & STYLE (Hormozi x Robbins):\n- DIRECT & CONFIDENT: Do not use weak words like \"just checking,\" \"maybe,\" or \"hope.\"\n- HIGH STATUS: You are an expert consultant, not a subservient bot.\n- CONCISE: 2-3 sentences max.\n- PATTERN INTERRUPT: Use short, punchy questions.\n\nCONVERSATION FLOW:\n- IF FIRST REPLY after Blueprint delivery: \"I sent the Blueprint over. Did you see the part on Page 3 about the $118k Revenue Leak? That number usually shocks providers.\"\n- IF THEY ASK \"How does it work?\": \"We install an n8n workflow that monitors your DMs 24/7. It replies in under 60 seconds, qualifies the participant, and auto-sends the Service Agreement. It turns a 7-day process into 7 minutes.\"\n- IF THEY ASK PRICE: \"It depends on volume, but it's less than the cost of missing *one* participant inquiry ($39k LTV). Are you currently handling intake yourself, or do you have a team?\"\n- TO BOOK CALL: \"I can show you the live logic in a 5-minute Revenue Audit. You'll see exactly how we recover that lost $118k. Opposed to a quick screen share this week?\"\n\nHANDOFF TRIGGERS (Set needs_human = true):\n- Complex NDIS compliance questions.\n- Specific technical integration questions beyond n8n/Airtable.\n- Explicitly asking to speak to Rayan.\n\nCurrent Offer: The NDIS Capacity Unlock Blueprint.\nResult: Stop the $118k bleed."
},
{
"content": "=**Conversation history:**\n{{ $json[\"conversation_history\"] || 'No previous messages' }}\n\n**Latest message from {{ $json[\"body\"][\"platform\"] }}:**\n\"{{ $json[\"body\"][\"message\"] }}\"\n\nSender: {{ $json[\"body\"][\"sender_name\"] }}\nProspect Business: {{ $json[\"prospect_business_name\"] || $json[\"business_name\"] }}\nProspect Role: {{ $json[\"prospect_role\"] }}"
}
]
},
"options": {},
"requestOptions": {}
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "AI Conversation Agent",
"type": "n8n-nodes-base.openAi",
"typeVersion": 1,
"position": [
11712,
22320
],
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "return JSON.parse($input.all()[0].json.message.content);"
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Parse AI Response",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
11952,
22320
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond-human",
"leftValue": "={{ String($json.needs_human) }}",
"rightValue": "true",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Needs Human?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
12192,
22320
]
},
{
"parameters": {
"jsCode": "const AT_PAT = '{{YOUR_AIRTABLE_API_KEY}}';\nconst AT_API = 'https://api.airtable.com/v0/appGx62c5AqsgnOVu';\nconst AT_H = {'Authorization': `Bearer ${AT_PAT}`, 'Content-Type': 'application/json'};\nconst LEADS = 'tbl15eFzJ96VJmRZX';\nconst CONVOS = 'tblsIY2xpFS8alEEi';\nconst LOG = 'tbliZ4lc2V4EPSiad';\nconst ai = $('Parse AI Response').item.json;\nconst prospect = $('Set Prospect Context').item.json;\nconst now = new Date().toISOString();\ntry {\n // Update Lead\n const leadData = await this.helpers.httpRequest({\n method: 'PATCH',\n url: `${AT_API}/${LEADS}`,\n headers: AT_H,\n body: {\n typecast: true,\n performUpsert: { fieldsToMergeOn: ['prospect_id'] },\n records: [{ fields: {\n prospect_id: prospect.prospect_id,\n 'Conversation Mode': 'Human Only',\n 'Lead Status': 'Engaged',\n 'Assigned To': 'Rayan',\n 'Last Message Date': now\n }}]\n }\n });\n const leadId = leadData.records?.[0]?.id;\n\n // Update Conversation\n const convData = await this.helpers.httpRequest({\n method: 'GET',\n url: `${AT_API}/${CONVOS}?filterByFormula=${encodeURIComponent(`{Instagram Thread ID}='${prospect.prospect_id}'`)}`,\n headers: AT_H\n });\n const convId = convData.records?.[0]?.id;\n if (convId) {\n await this.helpers.httpRequest({\n method: 'PATCH',\n url: `${AT_API}/${CONVOS}/${convId}`,\n headers: AT_H,\n body: {\n typecast: true,\n fields: {\n 'Status': 'Needs Handoff',\n 'Handoff Reason': ai.handoff_reason || 'AI flagged for human review',\n 'Last AI Classification': 'Needs Human'\n }\n }\n });\n }\n\n // Activity Log\n await this.helpers.httpRequest({\n method: 'POST',\n url: `${AT_API}/${LOG}`,\n headers: AT_H,\n body: {\n typecast: true,\n records: [{ fields: {\n 'Activity Label': 'Human Handoff',\n 'Lead': leadId ? [leadId] : [],\n 'Action': 'Human Handoff',\n 'Description': `Reason: ${ai.handoff_reason || 'AI flagged'}`,\n 'Triggered By': 'AI',\n 'Timestamp': now\n }}]\n }\n });\n} catch(e) {}\nreturn [{ json: { ...prospect, ...ai } }];"
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Flag for Human Review",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
12432,
22080
],
"continueOnFail": true
},
{
"parameters": {
"method": "POST",
"url": "https://graph.facebook.com/v21.0/me/messages",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"recipient\": {\n \"id\": \"{{ $('Transform Webhook Payload').item.json.body.sender_id }}\"\n },\n \"message\": {\n \"text\": \"{{ $json.reply_text || $('Parse AI Response').item.json.reply_text }}\"\n },\n \"access_token\": \"EAASVwxe8SZCsBQnhAZArnCM2yEj9sfBW5PtuGFe7EQHtinl6FQPcUWPWRCSAfOhuNkLMtIDvx2ZA25GcLQ4dbZBUoW3vuJSC1G2eQ8hmImntldibfRtpLxmdTBY3ZCkuhgvsk3PYyZBGh5KGJZA7bwAiStT5Ehe8zMz8Dl05m1R44TJUlClQNSxVRUtkqfhh4EwNSyV7t5QZC6VJIfcaRTJ5jrgFVxhXwJJ3XlFycAZDZD\"\n}",
"options": {}
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Send Handoff Reply",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
12672,
22080
],
"continueOnFail": true
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"leftValue": "={{ $json[\"classification\"] }}",
"rightValue": "interested",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Interested"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"leftValue": "={{ $json[\"classification\"] }}",
"rightValue": "neutral",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Neutral"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"leftValue": "={{ $json[\"classification\"] }}",
"rightValue": "objection",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Objection"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"leftValue": "={{ $json[\"classification\"] }}",
"rightValue": "not_interested",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Not Interested"
}
]
},
"options": {}
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Route by Intent",
"type": "n8n-nodes-base.switch",
"typeVersion": 3,
"position": [
12432,
22320
]
},
{
"parameters": {
"method": "POST",
"url": "https://graph.facebook.com/v21.0/me/messages",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"recipient\": {\n \"id\": \"{{ $('Transform Webhook Payload').item.json.body.sender_id }}\"\n },\n \"message\": {\n \"text\": \"{{ $json[\"reply_text\"] }}\"\n },\n \"access_token\": \"EAASVwxe8SZCsBQnhAZArnCM2yEj9sfBW5PtuGFe7EQHtinl6FQPcUWPWRCSAfOhuNkLMtIDvx2ZA25GcLQ4dbZBUoW3vuJSC1G2eQ8hmImntldibfRtpLxmdTBY3ZCkuhgvsk3PYyZBGh5KGJZA7bwAiStT5Ehe8zMz8Dl05m1R44TJUlClQNSxVRUtkqfhh4EwNSyV7t5QZC6VJIfcaRTJ5jrgFVxhXwJJ3XlFycAZDZD\"\n}",
"options": {}
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Send Reply (IG/Messenger)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
12672,
22320
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "const AT_PAT = '{{YOUR_AIRTABLE_API_KEY}}';\nconst AT_API = 'https://api.airtable.com/v0/appGx62c5AqsgnOVu';\nconst AT_H = {'Authorization': `Bearer ${AT_PAT}`, 'Content-Type': 'application/json'};\nconst LEADS = 'tbl15eFzJ96VJmRZX';\nconst CONVOS = 'tblsIY2xpFS8alEEi';\nconst MSGS = 'tbliyoKpfsFpNMEwp';\nconst LOG = 'tbliZ4lc2V4EPSiad';\nconst prospect = $('Set Prospect Context').item.json;\nconst ai = $('Parse AI Response').item.json;\nconst body = prospect.body || {};\nconst now = new Date().toISOString();\n\ntry {\n const statusMap = {interested: 'Interested', neutral: 'Contacted', objection: 'Contacted', not_interested: 'Not Interested'};\n const stageMap = {opening: 'Opening', discovery: 'Discovery', value_bridge: 'Value Bridge', booking: 'Booking'};\n const classMap = {interested: 'Interested', neutral: 'Neutral', objection: 'Objection', not_interested: 'Not Interested'};\n\n // 1. Upsert Lead\n const leadData = await this.helpers.httpRequest({\n method: 'PATCH',\n url: `${AT_API}/${LEADS}`,\n headers: AT_H,\n body: {\n typecast: true,\n performUpsert: { fieldsToMergeOn: ['prospect_id'] },\n records: [{ fields: {\n prospect_id: prospect.prospect_id,\n 'Name': body.sender_name || '',\n 'Platform': body.platform === 'instagram' ? 'Instagram' : 'Facebook Messenger',\n 'Lead Status': statusMap[ai.classification] || 'Contacted',\n 'Pipeline Stage': 'Outreach Sent',\n 'Last Message Date': now,\n 'Conversation Mode': 'AI Auto',\n 'Assigned To': 'AI',\n 'Lead Notes': ai.notes_for_crm || ''\n }}]\n }\n });\n const leadId = leadData.records?.[0]?.id;\n\n // 2. Find or create Conversation\n let convId;\n const convResult = await this.helpers.httpRequest({\n method: 'GET',\n url: `${AT_API}/${CONVOS}?filterByFormula=${encodeURIComponent(`{Instagram Thread ID}='${prospect.prospect_id}'`)}`,\n headers: AT_H\n });\n\n if (convResult.records?.length > 0) {\n convId = convResult.records[0].id;\n await this.helpers.httpRequest({\n method: 'PATCH',\n url: `${AT_API}/${CONVOS}/${convId}`,\n headers: AT_H,\n body: {\n typecast: true,\n fields: {\n 'Status': 'Active - AI',\n 'Current Follow-up Step': 'Initial Outreach',\n 'Conversation Stage': stageMap[ai.conversation_stage] || 'Opening',\n 'Last AI Classification': classMap[ai.classification] || 'Neutral',\n 'Last Message Direction': 'Outbound',\n 'Last Message Preview': (ai.reply_text || '').substring(0, 100),\n 'Last Message Timestamp': now,\n 'AI Summary': ai.notes_for_crm || ''\n }\n }\n });\n } else {\n const convCreateData = await this.helpers.httpRequest({\n method: 'POST',\n url: `${AT_API}/${CONVOS}`,\n headers: AT_H,\n body: {\n typecast: true,\n records: [{ fields: {\n 'Conversation Label': `${body.sender_name || 'Unknown'} - ${body.platform || 'DM'}`,\n 'Lead': leadId ? [leadId] : [],\n 'Instagram Thread ID': prospect.prospect_id,\n 'Platform': body.platform === 'instagram' ? 'Instagram' : 'Facebook Messenger',\n 'Status': 'Active - AI',\n 'Current Follow-up Step': 'Initial Outreach',\n 'Conversation Stage': stageMap[ai.conversation_stage] || 'Opening',\n 'Last AI Classification': classMap[ai.classification] || 'Neutral',\n 'Last Message Direction': 'Outbound',\n 'Last Message Preview': (ai.reply_text || '').substring(0, 100),\n 'Last Message Timestamp': now,\n 'AI Summary': ai.notes_for_crm || '',\n 'Created At': now\n }}]\n }\n });\n convId = convCreateData.records?.[0]?.id;\n }\n\n // 3. Log inbound message\n await this.helpers.httpRequest({\n method: 'POST',\n url: `${AT_API}/${MSGS}`,\n headers: AT_H,\n body: {\n typecast: true,\n records: [{ fields: {\n 'Message Label': `In: ${(body.message || '').substring(0, 50)}`,\n 'Conversation': convId ? [convId] : [],\n 'Direction': 'Inbound',\n 'Sent By': 'Lead',\n 'Message Text': body.message || '',\n 'Message Type': 'Text',\n 'Follow-up Step': 'Inbound Reply',\n 'Timestamp': body.timestamp ? new Date(Number(body.timestamp)).toISOString() : now,\n 'Requires Response': false\n }}]\n }\n });\n\n // 4. Log outbound message\n await this.helpers.httpRequest({\n method: 'POST',\n url: `${AT_API}/${MSGS}`,\n headers: AT_H,\n body: {\n typecast: true,\n records: [{ fields: {\n 'Message Label': `Out: ${(ai.reply_text || '').substring(0, 50)}`,\n 'Conversation': convId ? [convId] : [],\n 'Direction': 'Outbound',\n 'Sent By': 'AI',\n 'Message Text': ai.reply_text || '',\n 'Message Type': 'Text',\n 'Follow-up Step': 'Initial Outreach',\n 'AI Classification': classMap[ai.classification] || 'Neutral',\n 'Timestamp': now,\n 'Delivered': true\n }}]\n }\n });\n\n // 5. Activity Log\n await this.helpers.httpRequest({\n method: 'POST',\n url: `${AT_API}/${LOG}`,\n headers: AT_H,\n body: {\n typecast: true,\n records: [{ fields: {\n 'Activity Label': `AI replied to ${body.sender_name || 'Unknown'}`,\n 'Lead': leadId ? [leadId] : [],\n 'Action': 'Message Sent',\n 'Description': `Classification: ${ai.classification} | Stage: ${ai.conversation_stage} | Reply: \"${(ai.reply_text || '').substring(0, 200)}\"`,\n 'Triggered By': 'AI',\n 'Timestamp': now\n }}]\n }\n });\n} catch(e) {}\n\nreturn [{ json: { ...prospect, ...ai, status: 'awaiting_reply' } }];"
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Log Conversation",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
12912,
22320
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "const AT_PAT = '{{YOUR_AIRTABLE_API_KEY}}';\nconst AT_API = 'https://api.airtable.com/v0/appGx62c5AqsgnOVu';\nconst AT_H = {'Authorization': `Bearer ${AT_PAT}`, 'Content-Type': 'application/json'};\nconst LEADS = 'tbl15eFzJ96VJmRZX';\nconst LOG = 'tbliZ4lc2V4EPSiad';\nconst ai = $('Parse AI Response').item.json;\nconst prospect = $('Set Prospect Context').item.json;\nconst now = new Date().toISOString();\ntry {\n const leadData = await this.helpers.httpRequest({\n method: 'PATCH',\n url: `${AT_API}/${LEADS}`,\n headers: AT_H,\n body: {\n typecast: true,\n performUpsert: { fieldsToMergeOn: ['prospect_id'] },\n records: [{ fields: {\n prospect_id: prospect.prospect_id,\n 'Lead Status': 'Not Interested',\n 'Pipeline Stage': 'Closed',\n 'Last Message Date': now,\n 'Lead Notes': ai.notes_for_crm || ''\n }}]\n }\n });\n const leadId = leadData.records?.[0]?.id;\n\n await this.helpers.httpRequest({\n method: 'POST',\n url: `${AT_API}/${LOG}`,\n headers: AT_H,\n body: {\n typecast: true,\n records: [{ fields: {\n 'Activity Label': 'Lead Not Interested',\n 'Lead': leadId ? [leadId] : [],\n 'Action': 'Status Changed',\n 'Description': `Lead classified as not interested. Notes: ${ai.notes_for_crm || ''}`,\n 'Triggered By': 'AI',\n 'Timestamp': now\n }}]\n }\n });\n} catch(e) {}\nreturn [{ json: { ...prospect, ...ai } }];"
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Log Rejection",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
12672,
22560
],
"continueOnFail": true
},
{
"parameters": {
"amount": 4
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Wait 4 Hours",
"type": "n8n-nodes-base.wait",
"typeVersion": 1,
"position": [
13152,
22320
]
},
{
"parameters": {
"jsCode": "const AT_PAT = '{{YOUR_AIRTABLE_API_KEY}}';\nconst AT_API = 'https://api.airtable.com/v0/appGx62c5AqsgnOVu';\nconst LEADS = 'tbl15eFzJ96VJmRZX';\nconst prospect = $('Set Prospect Context').item.json;\n\n// 1. Fetch the latest Lead Status from Airtable\ntry {\n const leadData = await this.helpers.httpRequest({\n method: 'GET',\n url: `${AT_API}/${LEADS}?filterByFormula=${encodeURIComponent(`{prospect_id}='${prospect.prospect_id}'`)}`,\n headers: {'Authorization': `Bearer ${AT_PAT}`, 'Content-Type': 'application/json'}\n });\n\n if (!leadData.records || leadData.records.length === 0) return []; // Lead lost? Stop.\n\n const lead = leadData.records[0].fields;\n\n // 2. STATUS CHECK: Stop if they replied or booked\n const stopStatuses = ['Engaged', 'Interested', 'Appointment Booked', 'Stop', 'Human Only', 'Cold'];\n if (stopStatuses.includes(lead['Lead Status']) || lead['Conversation Mode'] === 'Human Only') {\n return []; \n }\n\n // 3. TIME CHECK: Did they message us recently?\n // If the lead sent a message in the last 30 mins, the 'Wait' node might have missed it.\n if (lead['Last Message Date']) {\n const lastMsgDate = new Date(lead['Last Message Date']);\n const now = new Date();\n const hoursSinceLastMsg = (now - lastMsgDate) / (1000 * 60 * 60);\n\n // If they messaged less than 30 mins ago, abort the follow-up.\n if (hoursSinceLastMsg < 0.5) {\n return [];\n }\n }\n\n} catch(e) {\n // If Airtable fails, better to skip follow-up than spam.\n return [];\n}\n\n// Pass data to the AI node\nconst parsed = $('Parse AI Response').item.json;\nreturn [{ json: { ...prospect, ...parsed } }];"
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Should Follow Up? (1hr)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
13392,
22320
],
"continueOnFail": true
},
{
"parameters": {
"resource": "chat",
"model": "gpt-4-turbo",
"prompt": {
"messages": [
{
"role": "system",
"content": "You are a direct, helpful assistant. The prospect received our \"NDIS Capacity Unlock Blueprint\" 4 hours ago but hasn't replied.\n\nYOUR GOAL:\nSend a 1-2 sentence \"nudge\" that references a specific mechanism in the PDF to reignite curiosity.\n\nTHE SCRIPT:\n\"Hey {{ $json[\"sender_name\"] }}, realized I didn't mention\u2014the 'Smart Intake' logic (Step 2 in the doc) specifically solves the endless phone tag with participants. Is your team currently chasing these manually?\"\n\nRETURN ONLY THE TEXT. NO QUOTES."
},
{
"content": "=Prospect: {{ $json[\"sender_name\"] }} from {{ $json[\"prospect_business_name\"] || $json[\"business_name\"] }}\nYour last message was: \"{{ $json[\"reply_text\"] }}\"\nWrite a casual 1-hour follow-up."
}
]
},
"options": {},
"requestOptions": {}
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Follow-up 1 (AI)",
"type": "n8n-nodes-base.openAi",
"typeVersion": 1,
"position": [
13632,
22320
],
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://graph.facebook.com/v21.0/me/messages",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"recipient\": {\n \"id\": \"{{ $('Transform Webhook Payload').item.json.body.sender_id }}\"\n },\n \"message\": {\n \"text\": \"{{ $json[\"message\"][\"content\"] }}\"\n },\n \"access_token\": \"EAASVwxe8SZCsBQnhAZArnCM2yEj9sfBW5PtuGFe7EQHtinl6FQPcUWPWRCSAfOhuNkLMtIDvx2ZA25GcLQ4dbZBUoW3vuJSC1G2eQ8hmImntldibfRtpLxmdTBY3ZCkuhgvsk3PYyZBGh5KGJZA7bwAiStT5Ehe8zMz8Dl05m1R44TJUlClQNSxVRUtkqfhh4EwNSyV7t5QZC6VJIfcaRTJ5jrgFVxhXwJJ3XlFycAZDZD\"\n}",
"options": {}
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Send Follow-up 1",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
13872,
22320
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "const AT_PAT = '{{YOUR_AIRTABLE_API_KEY}}';\nconst AT_API = 'https://api.airtable.com/v0/appGx62c5AqsgnOVu';\nconst AT_H = {'Authorization': `Bearer ${AT_PAT}`, 'Content-Type': 'application/json'};\nconst LEADS = 'tbl15eFzJ96VJmRZX';\nconst CONVOS = 'tblsIY2xpFS8alEEi';\nconst MSGS = 'tbliyoKpfsFpNMEwp';\nconst LOG = 'tbliZ4lc2V4EPSiad';\nconst prospect = $('Set Prospect Context').item.json;\nconst msgContent = $input.all()[0].json;\nconst followupText = msgContent.message?.content || msgContent.text || '';\nconst now = new Date().toISOString();\ntry {\n // Update Lead\n await this.helpers.httpRequest({\n method: 'PATCH',\n url: `${AT_API}/${LEADS}`,\n headers: AT_H,\n body: {\n typecast: true,\n performUpsert: { fieldsToMergeOn: ['prospect_id'] },\n records: [{ fields: {\n prospect_id: prospect.prospect_id,\n 'Pipeline Stage': 'Follow-up 1',\n 'Last Message Date': now,\n 'Next Follow-up Due': new Date(Date.now() + 24*60*60*1000).toISOString()\n }}]\n }\n });\n\n // Find conversation and update\n const convData = await this.helpers.httpRequest({\n method: 'GET',\n url: `${AT_API}/${CONVOS}?filterByFormula=${encodeURIComponent(`{Instagram Thread ID}='${prospect.prospect_id}'`)}`,\n headers: AT_H\n });\n const convId = convData.records?.[0]?.id;\n if (convId) {\n await this.helpers.httpRequest({\n method: 'PATCH',\n url: `${AT_API}/${CONVOS}/${convId}`,\n headers: AT_H,\n body: {\n typecast: true,\n fields: {\n 'Current Follow-up Step': 'Follow-up 1',\n 'Status': 'Awaiting Reply',\n 'Last Message Direction': 'Outbound',\n 'Last Message Preview': followupText.substring(0, 100),\n 'Last Message Timestamp': now\n }\n }\n });\n\n // Create Message record\n await this.helpers.httpRequest({\n method: 'POST',\n url: `${AT_API}/${MSGS}`,\n headers: AT_H,\n body: {\n typecast: true,\n records: [{ fields: {\n 'Message Label': 'Follow-up 1',\n 'Conversation': [convId],\n 'Direction': 'Outbound',\n 'Sent By': 'AI',\n 'Message Text': followupText,\n 'Message Type': 'Text',\n 'Follow-up Step': 'Follow-up 1',\n 'Timestamp': now,\n 'Delivered': true\n }}]\n }\n });\n }\n\n // Activity Log\n await this.helpers.httpRequest({\n method: 'POST',\n url: `${AT_API}/${LOG}`,\n headers: AT_H,\n body: {\n typecast: true,\n records: [{ fields: {\n 'Activity Label': 'Follow-up 1 Sent',\n 'Action': 'Follow-up Sent',\n 'Description': `1-hour follow-up: \"${followupText.substring(0, 200)}\"`,\n 'Triggered By': 'AI',\n 'Timestamp': now\n }}]\n }\n });\n} catch(e) {}\nreturn [{ json: { ...prospect, followup_step: 1 } }];"
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Log Follow-up 1",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
14112,
22320
],
"continueOnFail": true
},
{
"parameters": {
"amount": 20
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Wait 20 Hours",
"type": "n8n-nodes-base.wait",
"typeVersion": 1,
"position": [
14352,
22560
]
},
{
"parameters": {
"jsCode": "const AT_PAT = '{{YOUR_AIRTABLE_API_KEY}}';\nconst AT_API = 'https://api.airtable.com/v0/appGx62c5AqsgnOVu';\nconst LEADS = 'tbl15eFzJ96VJmRZX';\nconst prospect = $('Set Prospect Context').item.json;\n\n// 1. Fetch the latest Lead Status from Airtable\ntry {\n const leadData = await this.helpers.httpRequest({\n method: 'GET',\n url: `${AT_API}/${LEADS}?filterByFormula=${encodeURIComponent(`{prospect_id}='${prospect.prospect_id}'`)}`,\n headers: {'Authorization': `Bearer ${AT_PAT}`, 'Content-Type': 'application/json'}\n });\n\n if (!leadData.records || leadData.records.length === 0) return []; // Lead lost? Stop.\n\n const lead = leadData.records[0].fields;\n\n // 2. STATUS CHECK: Stop if they replied or booked\n const stopStatuses = ['Engaged', 'Interested', 'Appointment Booked', 'Stop', 'Human Only', 'Cold'];\n if (stopStatuses.includes(lead['Lead Status']) || lead['Conversation Mode'] === 'Human Only') {\n return []; \n }\n\n // 3. TIME CHECK: Did they message us recently?\n // If the lead sent a message in the last 30 mins, the 'Wait' node might have missed it.\n if (lead['Last Message Date']) {\n const lastMsgDate = new Date(lead['Last Message Date']);\n const now = new Date();\n const hoursSinceLastMsg = (now - lastMsgDate) / (1000 * 60 * 60);\n\n // If they messaged less than 30 mins ago, abort the follow-up.\n if (hoursSinceLastMsg < 0.5) {\n return [];\n }\n }\n\n} catch(e) {\n // If Airtable fails, better to skip follow-up than spam.\n return [];\n}\n\n// Pass data to the AI node\nconst parsed = $('Parse AI Response').item.json;\nreturn [{ json: { ...prospect, ...parsed } }];"
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Should Follow Up? (24hr)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
14592,
22560
],
"continueOnFail": true
},
{
"parameters": {
"resource": "chat",
"model": "gpt-4-turbo",
"prompt": {
"messages": [
{
"role": "system",
"content": "You are a results-focused assistant. The prospect is ghosting. We need to hit them with Hard Proof to build certainty.\n\nYOUR GOAL:\nShare a mini-case study of a similar provider to lower their skepticism.\n\nTHE SCRIPT:\n\"We just deployed the 'Zero-Latency' agent for another provider in Perth. They recovered 3 'lost' participants in the first 48 hours ($100k+ LTV). Just wanted to see if fixing that speed-to-lead is a priority for you right now?\"\n\nRETURN ONLY THE TEXT. NO QUOTES."
},
{
"content": "=Prospect: {{ $json[\"sender_name\"] }} from {{ $json[\"prospect_business_name\"] || $json[\"business_name\"] }}\nYour last message was: \"{{ $json[\"reply_text\"] }}\"\nYour offer: {{ $json[\"offer_description\"] }}\nResults: {{ $json[\"offer_outcomes\"] }}\nWrite a value-driven 24-hour follow-up."
}
]
},
"options": {},
"requestOptions": {}
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Follow-up 2 (AI)",
"type": "n8n-nodes-base.openAi",
"typeVersion": 1,
"position": [
14832,
22560
],
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://graph.facebook.com/v21.0/me/messages",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"recipient\": {\n \"id\": \"{{ $('Transform Webhook Payload').item.json.body.sender_id }}\"\n },\n \"message\": {\n \"text\": \"{{ $json[\"message\"][\"content\"] }}\"\n },\n \"access_token\": \"EAASVwxe8SZCsBQnhAZArnCM2yEj9sfBW5PtuGFe7EQHtinl6FQPcUWPWRCSAfOhuNkLMtIDvx2ZA25GcLQ4dbZBUoW3vuJSC1G2eQ8hmImntldibfRtpLxmdTBY3ZCkuhgvsk3PYyZBGh5KGJZA7bwAiStT5Ehe8zMz8Dl05m1R44TJUlClQNSxVRUtkqfhh4EwNSyV7t5QZC6VJIfcaRTJ5jrgFVxhXwJJ3XlFycAZDZD\"\n}",
"options": {}
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Send Follow-up 2",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
15072,
22560
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "const AT_PAT = '{{YOUR_AIRTABLE_API_KEY}}';\nconst AT_API = 'https://api.airtable.com/v0/appGx62c5AqsgnOVu';\nconst AT_H = {'Authorization': `Bearer ${AT_PAT}`, 'Content-Type': 'application/json'};\nconst LEADS = 'tbl15eFzJ96VJmRZX';\nconst CONVOS = 'tblsIY2xpFS8alEEi';\nconst MSGS = 'tbliyoKpfsFpNMEwp';\nconst LOG = 'tbliZ4lc2V4EPSiad';\nconst prospect = $('Set Prospect Context').item.json;\nconst msgContent = $input.all()[0].json;\nconst followupText = msgContent.message?.content || msgContent.text || '';\nconst now = new Date().toISOString();\ntry {\n // Update Lead\n await this.helpers.httpRequest({\n method: 'PATCH',\n url: `${AT_API}/${LEADS}`,\n headers: AT_H,\n body: {\n typecast: true,\n performUpsert: { fieldsToMergeOn: ['prospect_id'] },\n records: [{ fields: {\n prospect_id: prospect.prospect_id,\n 'Pipeline Stage': 'Follow-up 2',\n 'Last Message Date': now,\n 'Next Follow-up Due': new Date(Date.now() + 48*60*60*1000).toISOString()\n }}]\n }\n });\n\n // Find conversation and update\n const convData = await this.helpers.httpRequest({\n method: 'GET',\n url: `${AT_API}/${CONVOS}?filterByFormula=${encodeURIComponent(`{Instagram Thread ID}='${prospect.prospect_id}'`)}`,\n headers: AT_H\n });\n const convId = convData.records?.[0]?.id;\n if (convId) {\n await this.helpers.httpRequest({\n method: 'PATCH',\n url: `${AT_API}/${CONVOS}/${convId}`,\n headers: AT_H,\n body: {\n typecast: true,\n fields: {\n 'Current Follow-up Step': 'Follow-up 2',\n 'Status': 'Awaiting Reply',\n 'Last Message Direction': 'Outbound',\n 'Last Message Preview': followupText.substring(0, 100),\n 'Last Message Timestamp': now\n }\n }\n });\n\n // Create Message record\n await this.helpers.httpRequest({\n method: 'POST',\n url: `${AT_API}/${MSGS}`,\n headers: AT_H,\n body: {\n typecast: true,\n records: [{ fields: {\n 'Message Label': 'Follow-up 2',\n 'Conversation': [convId],\n 'Direction': 'Outbound',\n 'Sent By': 'AI',\n 'Message Text': followupText,\n 'Message Type': 'Text',\n 'Follow-up Step': 'Follow-up 2',\n 'Timestamp': now,\n 'Delivered': true\n }}]\n }\n });\n }\n\n // Activity Log\n await this.helpers.httpRequest({\n method: 'POST',\n url: `${AT_API}/${LOG}`,\n headers: AT_H,\n body: {\n typecast: true,\n records: [{ fields: {\n 'Activity Label': 'Follow-up 2 Sent',\n 'Action': 'Follow-up Sent',\n 'Description': `24-hour follow-up: \"${followupText.substring(0, 200)}\"`,\n 'Triggered By': 'AI',\n 'Timestamp': now\n }}]\n }\n });\n} catch(e) {}\nreturn [{ json: { ...prospect, followup_step: 2 } }];"
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Log Follow-up 2",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
15312,
22560
],
"continueOnFail": true
},
{
"parameters": {
"amount": 48
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Wait 48 Hours",
"type": "n8n-nodes-base.wait",
"typeVersion": 1,
"position": [
15552,
22800
]
},
{
"parameters": {
"jsCode": "const AT_PAT = '{{YOUR_AIRTABLE_API_KEY}}';\nconst AT_API = 'https://api.airtable.com/v0/appGx62c5AqsgnOVu';\nconst LEADS = 'tbl15eFzJ96VJmRZX';\nconst prospect = $('Set Prospect Context').item.json;\n\n// 1. Fetch the latest Lead Status from Airtable\ntry {\n const leadData = await this.helpers.httpRequest({\n method: 'GET',\n url: `${AT_API}/${LEADS}?filterByFormula=${encodeURIComponent(`{prospect_id}='${prospect.prospect_id}'`)}`,\n headers: {'Authorization': `Bearer ${AT_PAT}`, 'Content-Type': 'application/json'}\n });\n\n if (!leadData.records || leadData.records.length === 0) return []; // Lead lost? Stop.\n\n const lead = leadData.records[0].fields;\n\n // 2. STATUS CHECK: Stop if they replied or booked\n const stopStatuses = ['Engaged', 'Interested', 'Appointment Booked', 'Stop', 'Human Only', 'Cold'];\n if (stopStatuses.includes(lead['Lead Status']) || lead['Conversation Mode'] === 'Human Only') {\n return []; \n }\n\n // 3. TIME CHECK: Did they message us recently?\n // If the lead sent a message in the last 30 mins, the 'Wait' node might have missed it.\n if (lead['Last Message Date']) {\n const lastMsgDate = new Date(lead['Last Message Date']);\n const now = new Date();\n const hoursSinceLastMsg = (now - lastMsgDate) / (1000 * 60 * 60);\n\n // If they messaged less than 30 mins ago, abort the follow-up.\n if (hoursSinceLastMsg < 0.5) {\n return [];\n }\n }\n\n} catch(e) {\n // If Airtable fails, better to skip follow-up than spam.\n return [];\n}\n\n// Pass data to the AI node\nconst parsed = $('Parse AI Response').item.json;\nreturn [{ json: { ...prospect, ...parsed } }];"
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Should Follow Up? (48hr)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
15792,
22800
],
"continueOnFail": true
},
{
"parameters": {
"resource": "chat",
"model": "gpt-4-turbo",
"prompt": {
"messages": [
{
"role": "system",
"content": "You are a professional, high-status consultant. The prospect has ignored 3 messages. It is time to close the file.\n\nYOUR GOAL:\nSend a \"Breakup Message.\" Be gracious, professional, but firm that you are moving on. This removes the sales pressure.\n\nTHE SCRIPT:\n\"Hey {{ $json[\"sender_name\"] }}, assume this isn't a priority right now so I'll close this file. If you ever want to stop the manual intake grind, the Blueprint is yours to keep. Best, Rayan.\"\n\nRETURN ONLY THE TEXT. NO QUOTES."
},
{
"content": "=Prospect: {{ $json[\"sender_name\"] }} from {{ $json[\"prospect_business_name\"] || $json[\"business_name\"] }}\nThis is the final follow-up. Write a gracious closing message."
}
]
},
"options": {},
"requestOptions": {}
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Final Follow-up (AI)",
"type": "n8n-nodes-base.openAi",
"typeVersion": 1,
"position": [
16032,
22800
],
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://graph.facebook.com/v21.0/me/messages",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"recipient\": {\n \"id\": \"{{ $('Transform Webhook Payload').item.json.body.sender_id }}\"\n },\n \"message\": {\n \"text\": \"{{ $json[\"message\"][\"content\"] }}\"\n },\n \"access_token\": \"EAASVwxe8SZCsBQnhAZArnCM2yEj9sfBW5PtuGFe7EQHtinl6FQPcUWPWRCSAfOhuNkLMtIDvx2ZA25GcLQ4dbZBUoW3vuJSC1G2eQ8hmImntldibfRtpLxmdTBY3ZCkuhgvsk3PYyZBGh5KGJZA7bwAiStT5Ehe8zMz8Dl05m1R44TJUlClQNSxVRUtkqfhh4EwNSyV7t5QZC6VJIfcaRTJ5jrgFVxhXwJJ3XlFycAZDZD\"\n}",
"options": {}
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Send Final Follow-up",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
16272,
22800
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "const AT_PAT = '{{YOUR_AIRTABLE_API_KEY}}';\nconst AT_API = 'https://api.airtable.com/v0/appGx62c5AqsgnOVu';\nconst AT_H = {'Authorization': `Bearer ${AT_PAT}`, 'Content-Type': 'application/json'};\nconst LEADS = 'tbl15eFzJ96VJmRZX';\nconst CONVOS = 'tblsIY2xpFS8alEEi';\nconst LOG = 'tbliZ4lc2V4EPSiad';\nconst prospect = $('Set Prospect Context').item.json;\nconst now = new Date().toISOString();\ntry {\n await this.helpers.httpRequest({\n method: 'PATCH',\n url: `${AT_API}/${LEADS}`,\n headers: AT_H,\n body: {\n typecast: true,\n performUpsert: { fieldsToMergeOn: ['prospect_id'] },\n records: [{ fields: {\n prospect_id: prospect.prospect_id,\n 'Lead Status': 'Cold',\n 'Pipeline Stage': 'Follow-up 3'\n }}]\n }\n });\n\n // Update conversation to Closed Lost\n const convData = await this.helpers.httpRequest({\n method: 'GET',\n url: `${AT_API}/${CONVOS}?filterByFormula=${encodeURIComponent(`{Instagram Thread ID}='${prospect.prospect_id}'`)}`,\n headers: AT_H\n });\n const convId = convData.records?.[0]?.id;\n if (convId) {\n await this.helpers.httpRequest({\n method: 'PATCH',\n url: `${AT_API}/${CONVOS}/${convId}`,\n headers: AT_H,\n body: {\n typecast: true,\n fields: { 'Status': 'Closed Lost' }\n }\n });\n }\n\n // Activity Log\n await this.helpers.httpRequest({\n method: 'POST',\n url: `${AT_API}/${LOG}`,\n headers: AT_H,\n body: {\n typecast: true,\n records: [{ fields: {\n 'Activity Label': 'Marked as Cold',\n 'Action': 'Status Changed',\n 'Description': 'Lead marked as cold after 3 follow-ups with no response',\n 'Triggered By': 'System',\n 'Timestamp': now\n }}]\n }\n });\n} catch(e) {}\nreturn [{ json: { ...prospect, status: 'cold' } }];"
},
"id": "{{YOUR_NOTION_PAGE_ID}}",
"name": "Mark as Cold",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
16512,
22800
],
"continueOnFail": true
}
],
"connections": {
"Webhook Trigger": {
"main": [
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript": {
"main": [
[
{
"node": "Is Verification?",
"type": "main",
"index": 0
}
]
]
},
"Is Verification?": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond OK",
"type": "main",
"index": 0
}
]
]
},
"Respond OK": {
"main": [
[
{
"node": "Transform Webhook Payload",
"type": "main",
"index": 0
}
]
]
},
"Transform Webhook Payload": {
"main": [
[
{
"node": "Set Prospect Context",
"type": "main",
"index": 0
}
]
]
},
"Set Prospect Context": {
"main": [
[
{
"node": "Check Lead & Process?",
"type": "main",
"index": 0
}
]
]
},
"Check Lead & Process?": {
"main": [
[
{
"node": "Upsert Lead on Reply",
"type": "main",
"index": 0
}
]
]
},
"Upsert Lead on Reply": {
"main": [
[
{
"node": "Fetch Conversation",
"type": "main",
"index": 0
}
]
]
},
"Fetch Conversation": {
"main": [
[
{
"node": "Fetch Messages",
"type": "main",
"index": 0
}
]
]
},
"Fetch Messages": {
"main": [
[
{
"node": "Build Context",
"type": "main",
"index": 0
}
]
]
},
"Build Context": {
"main": [
[
{
"node": "Lead Enrichment (Mock)",
"type": "main",
"index": 0
}
]
]
},
"Lead Enrichment (Mock)": {
"main": [
[
{
"node": "AI Conversation Agent",
"type": "main",
"index": 0
}
]
]
},
"AI Conversation Agent": {
"main": [
[
{
"node": "Parse AI Response",
"type": "main",
"index": 0
}
]
]
},
"Parse AI Response": {
"main": [
[
{
"node": "Needs Human?",
"type": "main",
"index": 0
}
]
]
},
"Needs Human?": {
"main": [
[
{
"node": "Flag for Human Review",
"type": "main",
"index": 0
}
],
[
{
"node": "Route by Intent",
"type": "main",
"index": 0
}
]
]
},
"Flag for Human Review": {
"main": [
[
{
"node": "Send Handoff Reply",
"type": "main",
"index": 0
}
]
]
},
"Route by Intent": {
"main": [
[
{
"node": "Send Reply (IG/Messenger)",
"type": "main",
"index": 0
}
],
[
{
"node": "Send Reply (IG/Messenger)",
"type": "main",
"index": 0
}
],
[
{
"node": "Send Reply (IG/Messenger)",
"type": "main",
"index": 0
}
],
[
{
"node": "Log Rejection",
"type": "main",
"index": 0
}
]
]
},
"Send Reply (IG/Messenger)": {
"main": [
[
{
"node": "Log Conversation",
"type": "main",
"index": 0
}
]
]
},
"Log Conversation": {
"main": [
[
{
"node": "Wait 4 Hours",
"type": "main",
"index": 0
}
]
]
},
"Should Follow Up? (1hr)": {
"main": [
[
{
"node": "Follow-up 1 (AI)",
"type": "main",
"index": 0
}
]
]
},
"Follow-up 1 (AI)": {
"main": [
[
{
"node": "Send Follow-up 1",
"type": "main",
"index": 0
}
]
]
},
"Send Follow-up 1": {
"main": [
[
{
"node": "Log Follow-up 1",
"type": "main",
"index": 0
}
]
]
},
"Log Follow-up 1": {
"main": [
[
{
"node": "Wait 20 Hours",
"type": "main",
"index": 0
}
]
]
},
"Should Follow Up? (24hr)": {
"main": [
[
{
"node": "Follow-up 2 (AI)",
"type": "main",
"index": 0
}
]
]
},
"Follow-up 2 (AI)": {
"main": [
[
{
"node": "Send Follow-up 2",
"type": "main",
"index": 0
}
]
]
},
"Send Follow-up 2": {
"main": [
[
{
"node": "Log Follow-up 2",
"type": "main",
"index": 0
}
]
]
},
"Log Follow-up 2": {
"main": [
[
{
"node": "Wait 48 Hours",
"type": "main",
"index": 0
}
]
]
},
"Wait 48 Hours": {
"main": [
[
{
"node": "Should Follow Up? (48hr)",
"type": "main",
"index": 0
}
]
]
},
"Should Follow Up? (48hr)": {
"main": [
[
{
"node": "Final Follow-up (AI)",
"type": "main",
"index": 0
}
]
]
},
"Final Follow-up (AI)": {
"main": [
[
{
"node": "Send Final Follow-up",
"type": "main",
"index": 0
}
]
]
},
"Send Final Follow-up": {
"main": [
[
{
"node": "Mark as Cold",
"type": "main",
"index": 0
}
]
]
},
"Wait 4 Hours": {
"main": [
[
{
"node": "Should Follow Up? (1hr)",
"type": "main",
"index": 0
}
]
]
},
"Wait 20 Hours": {
"main": [
[
{
"node": "Should Follow Up? (24hr)",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"callerPolicy": "workflowsFromSameOwner",
"availableInMCP": false
},
"staticData": null,
"meta": null,
"versionId": "{{YOUR_NOTION_PAGE_ID}}",
"activeVersionId": null,
"versionCounter": 26,
"triggerCount": 1,
"shared": [
{
"updatedAt": "2026-02-14T13:38:37.229Z",
"createdAt": "2026-02-14T13:38:37.229Z",
"role": "workflow:owner",
"workflowId": "NpLSu1I9MQeowXaP",
"projectId": "7NOiDJlVGtj6HHEb",
"project":
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
openAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
BnB Outreach Replier (Airtable). Uses httpRequest, openAi. Webhook trigger; 37 nodes.
Source: https://gist.github.com/Iaminfitite/6356b1d45d4c65fe2065e2f8b2a9aecf — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This powerful n8n automation workflow is designed to execute advanced B2B lead enrichment and hyper-personalization for cold email outreach. By orchestrating a complex chain of data scraping, AI analy
Propulsar — Content Engine v3. Uses openAi, httpRequest, googleSheets. Webhook trigger; 73 nodes.
Eu Clara – Funil Kiwify Completo. Uses postgres, openAi, httpRequest, gmail. Webhook trigger; 70 nodes.
This workflow bridges the gap between raw product data and revenue sales tools. It automates the entire Product Qualified Lead (PQL) lifecycle—from real-time intent routing to churn prevention—reducin
Lua Nova - Sistema Completo. Uses postgres, httpRequest, openAi. Webhook trigger; 55 nodes.