This workflow corresponds to n8n.io template #9166 — we link there as the canonical source.
This workflow follows the Airtable → Chainllm 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 →
{
"nodes": [
{
"id": "5f0e216a-ea69-4674-af22-3fe170cbd766",
"name": "Merge Results",
"type": "n8n-nodes-base.merge",
"position": [
22416,
16208
],
"parameters": {},
"typeVersion": 3
},
{
"id": "b7116aa4-8d06-4ff4-8d74-da1b030a62f2",
"name": "Track Results",
"type": "n8n-nodes-base.code",
"position": [
22640,
16208
],
"parameters": {
"jsCode": "// Track which channels were used\nconst items = $input.all();\n\n// Check if SMS was sent (will have twilio response)\nconst smsSent = items.some(item => item.json.sid || item.json.messageSid);\n\n// Check if Email was sent (will have email response)\nconst emailSent = items.some(item => item.json.accepted || item.json.messageId);\n\n// Get customer data from Prepare Messages node\nconst customerData = $('Prepare Messages2').first().json;\n\nreturn {\n json: {\n ...customerData,\n sms_sent: smsSent,\n email_sent: emailSent,\n messages_sent_at: new Date().toISOString()\n }\n};"
},
"typeVersion": 2
},
{
"id": "58a9ef5a-8fb0-4781-8eb0-b4dd3eeb6ad0",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
19280,
15872
],
"parameters": {
"width": 928,
"height": 1392,
"content": "## \ud83d\udcde Missed Call Revenue Recovery Bot\n\n### \ud83d\udccb Setup Instructions\n\n**Step 1: Configure Twilio**\n\u2022 Add Twilio credential (Account SID + Auth Token)\n\u2022 Replace `+15551234567` with your Twilio phone number (2 SMS nodes)\n\u2022 Webhook auto-configures when workflow activates\n\n**Step 2: Configure Integrations**\n\ud83d\udd39 **Slack Channel**\n \u2022 Channel \u2192 View details \u2192 Copy Channel ID\n \u2022 Replace `REPLACE_WITH_SLACK_SALES_CHANNEL_ID`\n\n\ud83d\udd39 **Airtable**\n \u2022 Base ID from URL \u2192 `appXXXXXXXXXXXXXX`\n \u2022 Table ID from URL \u2192 `tblYYYYYYYYYYYY`\n \u2022 Columns: Caller_Number, Customer_Name, Call_Time, Priority, SMS_Sent, Email_Sent, Booking_Link, Status, Urgency_Score, Follow_Up_Date\n\n\ud83d\udd39 **Business Details**\n \u2022 Booking URL: Replace `https://cal.com/your-business`\n \u2022 Email: Replace `your-business@example.com`\n \u2022 Follow-up SMS: Replace `+1-555-0100` and `Your Business Name`\n\n**Step 3: Optional CRM**\n\u2022 Replace `https://your-crm-api.com` with your CRM endpoints\n\u2022 Add HTTP Header Auth credential\n\u2022 Or disable \"Check Existing Contact\" + \"Check if Booked\" nodes\n\n**Step 4: Credentials**\n\u2713 Twilio API \u2713 OpenAI (GPT-4o) \u2713 Slack OAuth2\n\u2713 Airtable Token \u2713 SMTP \u2713 CRM API (optional)\n\n---\n\n### \u2699\ufe0f Workflow\n**Missed Call** \u2192 **Analyze Context** \u2192 **CRM Lookup** \u2192 **AI Response** \u2192 **SMS/Email** \u2192 **Merge Results** \u2192 **Track Sent** \u2192 **Log Airtable** \u2192 **Slack Notify** \u2192 **24h Wait** \u2192 **Check Booking** \u2192 **Follow-up SMS**\n\n\u2022 AI personalizes based on time/customer status\n\u2022 Tracks SMS/Email delivery automatically\n\u2022 24h auto-follow-up if no booking detected"
},
"typeVersion": 1
},
{
"id": "f62d032e-17c4-498c-988a-434e2288a21c",
"name": "Missed Call Detected2",
"type": "n8n-nodes-base.twilioTrigger",
"position": [
20352,
16208
],
"parameters": {
"updates": [
"com.twilio.voice.insights.call-summary.complete"
]
},
"credentials": {
"twilioApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "fe521a57-2f91-45f5-a09d-efd7fd3cae9f",
"name": "Analyze Call Context2",
"type": "n8n-nodes-base.code",
"position": [
20576,
16208
],
"parameters": {
"jsCode": "// Extract and analyze call data\nconst callData = $input.first().json;\n\n// Twilio Call Status values:\n// 'completed' = answered and ended normally\n// 'no-answer' = no answer or rejected\n// 'busy' = busy signal\n// 'canceled' = hung up while queued/ringing\n// 'failed' = invalid number or error\n\n// Check if call was answered (CallDuration > 0 means conversation happened)\nconst callDuration = parseInt(callData.CallDuration) || 0;\nconst callStatus = callData.CallStatus || '';\n\n// Skip answered calls (completed with duration > 0)\nconst wasAnswered = callStatus === 'completed' && callDuration > 0;\n\nif (wasAnswered) {\n return []; // Don't process answered calls\n}\n\n// Extract caller information (handle both camelCase and PascalCase)\nconst callerNumber = callData.From || callData.from || '';\nconst receiverNumber = callData.To || callData.to || '';\nconst timestamp = callData.Timestamp || callData.timestamp || new Date().toISOString();\nconst callTime = new Date(timestamp);\nconst currentHour = callTime.getHours();\n\n// Determine business context\nconst isBusinessHours = currentHour >= 9 && currentHour < 17;\nconst dayOfWeek = callTime.getDay();\nconst isWeekend = dayOfWeek === 0 || dayOfWeek === 6;\n\nreturn {\n json: {\n caller_number: callerNumber,\n receiver_number: receiverNumber,\n call_time: callTime.toISOString(),\n call_time_local: callTime.toLocaleString(),\n call_status: callStatus,\n call_duration: callDuration,\n is_business_hours: isBusinessHours,\n is_weekend: isWeekend,\n call_sid: callData.CallSid || callData.callSid || '',\n urgency_score: !isBusinessHours ? 8 : 5, // Higher urgency for after-hours calls\n context: {\n time_of_day: currentHour < 12 ? 'morning' : currentHour < 17 ? 'afternoon' : 'evening',\n day_type: isWeekend ? 'weekend' : 'weekday'\n }\n }\n};"
},
"typeVersion": 2
},
{
"id": "e7a1c806-c63b-45f8-8bf5-51ffc12486d0",
"name": "Check Existing Contact2",
"type": "n8n-nodes-base.httpRequest",
"position": [
20800,
16208
],
"parameters": {
"url": "=https://your-crm-api.com/contacts/search?phone={{ $json.caller_number }}",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"typeVersion": 4.1
},
{
"id": "10faf764-b0e6-486b-92c7-8b08663af3ed",
"name": "Merge Customer Data2",
"type": "n8n-nodes-base.code",
"position": [
21024,
16208
],
"parameters": {
"jsCode": "// Merge call data with CRM history\nconst callData = $('Analyze Call Context2').first().json;\nconst crmData = $input.first().json;\n\nconst hasHistory = crmData && crmData.contact;\nconst contact = crmData?.contact || {};\n\nreturn {\n json: {\n ...callData,\n \n // CRM Information\n is_existing_customer: hasHistory,\n customer_name: contact.name || 'Unknown',\n customer_email: contact.email || null,\n last_contact_date: contact.last_contact || null,\n customer_status: contact.status || 'new',\n previous_purchases: contact.purchase_count || 0,\n lifetime_value: contact.lifetime_value || 0,\n notes: contact.notes || '',\n \n // Prioritization\n priority_level: hasHistory && contact.lifetime_value > 1000 ? 'high' : hasHistory ? 'medium' : 'new',\n follow_up_urgency: hasHistory ? 'immediate' : 'standard'\n }\n};"
},
"typeVersion": 2
},
{
"id": "be1b553a-725b-4a80-b901-1d469794bdaa",
"name": "Generate AI Response2",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
21280,
16208
],
"parameters": {
"text": "=Generate a personalized missed call response for the following situation:\n\nCustomer Information:\n- Name: {{ $json.customer_name }}\n- Status: {{ $json.is_existing_customer ? 'Existing Customer' : 'New Prospect' }}\n- Priority: {{ $json.priority_level }}\n- Previous Purchases: {{ $json.previous_purchases }}\n\nCall Context:\n- Time: {{ $json.context.time_of_day }} on a {{ $json.context.day_type }}\n- Business Hours: {{ $json.is_business_hours ? 'Yes' : 'No (After Hours)' }}\n- Phone: {{ $json.caller_number }}\n\nCreate TWO versions:\n\n1. SMS_MESSAGE (160 chars max): Friendly, apologetic, includes booking link placeholder\n2. EMAIL_MESSAGE (200 words): Professional, personalized, multiple contact options\n\nReturn JSON with:\n- sms: Brief apologetic message\n- email_subject: Personalized subject line\n- email_body: Professional email with [BOOKING_LINK] placeholder\n- tone: warm/professional/urgent\n- recommended_channel: sms/email/both\n\nTone should be:\n- Warm and apologetic\n- Acknowledge their attempt to reach us\n- Offer immediate booking option\n- Personalize based on customer status",
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.4
},
{
"id": "27649586-1171-4e61-8cf7-d1239ab8ce89",
"name": "OpenAI Chat Model8",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
21248,
16432
],
"parameters": {
"model": "gpt-4o",
"options": {
"maxTokens": 500,
"temperature": 0.7
}
},
"typeVersion": 1
},
{
"id": "d4e13bed-0f1b-41ea-a450-81b4e9ac5550",
"name": "Structured Output Parser8",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
21376,
16432
],
"parameters": {
"autoFix": true,
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"sms\": {\n \"type\": \"string\",\n \"description\": \"SMS message (160 chars max)\"\n },\n \"email_subject\": {\n \"type\": \"string\",\n \"description\": \"Email subject line\"\n },\n \"email_body\": {\n \"type\": \"string\",\n \"description\": \"Email body with [BOOKING_LINK] placeholder\"\n },\n \"tone\": {\n \"type\": \"string\",\n \"enum\": [\"warm\", \"professional\", \"urgent\"],\n \"description\": \"Message tone\"\n },\n \"recommended_channel\": {\n \"type\": \"string\",\n \"enum\": [\"sms\", \"email\", \"both\"],\n \"description\": \"Recommended communication channel\"\n }\n },\n \"required\": [\"sms\", \"email_subject\", \"email_body\", \"tone\", \"recommended_channel\"]\n}"
},
"typeVersion": 1.3
},
{
"id": "e16ca74e-3796-4f9f-8279-5aab98395133",
"name": "Prepare Messages2",
"type": "n8n-nodes-base.code",
"position": [
21744,
16208
],
"parameters": {
"jsCode": "// Parse AI response and prepare messages\nconst aiResponse = $input.first().json;\nconst customerData = $('Merge Customer Data2').first().json;\n\n// Generate booking link (Cal.com or Calendly)\nconst bookingUrl = 'https://cal.com/your-business'; // Replace with your booking link\nconst bookingLink = `${bookingUrl}?name=${encodeURIComponent(customerData.customer_name)}&phone=${encodeURIComponent(customerData.caller_number)}`;\n\n// Add booking link to messages\nconst smsWithLink = `${aiResponse.sms}\\n\\nBook instantly: ${bookingLink}`;\nconst emailWithLink = aiResponse.email_body.replace('[BOOKING_LINK]', bookingLink);\n\nreturn {\n json: {\n ...customerData,\n \n // AI-generated messages\n sms_message: smsWithLink,\n email_subject: aiResponse.email_subject,\n email_body: emailWithLink,\n message_tone: aiResponse.tone,\n recommended_channel: aiResponse.recommended_channel,\n \n // Booking information\n booking_link: bookingLink,\n \n // Metadata\n response_generated_at: new Date().toISOString()\n }\n};"
},
"typeVersion": 2
},
{
"id": "10884b30-701c-4c5f-8d92-abc00f9228a8",
"name": "Send SMS?2",
"type": "n8n-nodes-base.if",
"position": [
21968,
16112
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "sms-channel",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.recommended_channel }}",
"rightValue": "sms"
},
{
"id": "both-channels",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.recommended_channel }}",
"rightValue": "both"
}
]
}
},
"typeVersion": 2
},
{
"id": "52dce42a-1e0b-4389-bd9a-422b3dc04cba",
"name": "Send SMS2",
"type": "n8n-nodes-base.twilio",
"position": [
22192,
16112
],
"parameters": {
"to": "={{ $json.caller_number }}",
"from": "+1234567890",
"message": "={{ $json.sms_message }}",
"options": {}
},
"typeVersion": 1
},
{
"id": "5c68f5ef-2058-4d58-a257-b6779488f3cb",
"name": "Send Email?2",
"type": "n8n-nodes-base.if",
"position": [
21968,
16304
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "has-email",
"operator": {
"type": "string",
"operation": "notEmpty"
},
"leftValue": "={{ $json.customer_email }}",
"rightValue": ""
},
{
"id": "email-channel",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.recommended_channel }}",
"rightValue": "email"
}
]
}
},
"typeVersion": 2
},
{
"id": "a76db0f7-5e55-4be4-947f-dc9921779442",
"name": "Send Email2",
"type": "n8n-nodes-base.emailSend",
"position": [
22192,
16304
],
"parameters": {
"options": {},
"subject": "={{ $json.email_subject }}",
"toEmail": "={{ $json.customer_email }}",
"fromEmail": "user@example.com"
},
"credentials": {
"smtp": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "1ca48805-f52b-4e18-93f1-c79290d328a3",
"name": "Log to CRM2",
"type": "n8n-nodes-base.airtable",
"position": [
22864,
16208
],
"parameters": {
"base": {
"__rl": true,
"mode": "id",
"value": "REPLACE_WITH_AIRTABLE_BASE_ID"
},
"table": {
"__rl": true,
"mode": "id",
"value": "REPLACE_WITH_AIRTABLE_TABLE_ID"
},
"columns": {
"value": {
"Status": "Pending Response",
"Priority": "={{ $json.priority_level }}",
"SMS_Sent": "={{ $json.sms_sent ? 'Yes' : 'No' }}",
"Call_Time": "={{ $json.call_time }}",
"Email_Sent": "={{ $json.email_sent ? 'Yes' : 'No' }}",
"Booking_Link": "={{ $json.booking_link }}",
"Caller_Number": "={{ $json.caller_number }}",
"Customer_Name": "={{ $json.customer_name }}",
"Urgency_Score": "={{ $json.urgency_score }}",
"Follow_Up_Date": "={{ new Date(Date.now() + 86400000).toISOString() }}"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "upsert"
},
"typeVersion": 2
},
{
"id": "da785def-5222-4e81-a0ed-8b9ef698732d",
"name": "Notify Sales Team3",
"type": "n8n-nodes-base.slack",
"position": [
23088,
16208
],
"parameters": {
"text": "=\ud83d\udcde *Missed Call Alert*\n\n*Customer:* {{ $json.customer_name }}{{ $json.is_existing_customer ? ' (Existing Customer \ud83c\udf1f)' : ' (New Prospect)' }}\n*Phone:* {{ $json.caller_number }}\n*Time:* {{ $json.call_time_local }}\n*Priority:* {{ $json.priority_level.toUpperCase() }}\n\n*Context:*\n\u2022 Call during: {{ $json.is_business_hours ? 'Business Hours' : 'After Hours \u26a0\ufe0f' }}\n\u2022 Customer Status: {{ $json.customer_status }}\n{{ $json.previous_purchases > 0 ? '\u2022 Previous Purchases: ' + $json.previous_purchases : '' }}\n{{ $json.lifetime_value > 0 ? '\u2022 Lifetime Value: $' + $json.lifetime_value : '' }}\n\n*Auto-Response Sent:*\n{{ $json.sms_sent ? '\u2705 SMS' : '\u274c SMS' }} | {{ $json.email_sent ? '\u2705 Email' : '\u274c Email' }}\n\n*Booking Link:* {{ $json.booking_link }}\n\n\ud83c\udfaf *Action Required:* {{ $json.follow_up_urgency === 'immediate' ? 'Call back ASAP!' : 'Follow up within 24 hours' }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "REPLACE_WITH_SLACK_SALES_CHANNEL_ID"
},
"otherOptions": {
"includeLinkToWorkflow": false
}
},
"typeVersion": 2.1
},
{
"id": "a162a326-8ad2-499a-b6a1-5d004683570f",
"name": "Wait 24 Hours2",
"type": "n8n-nodes-base.wait",
"position": [
23312,
16208
],
"parameters": {
"unit": "hours",
"amount": 24
},
"typeVersion": 1.1
},
{
"id": "ed555f98-119d-4d52-822f-88730c1324e8",
"name": "Check if Booked2",
"type": "n8n-nodes-base.httpRequest",
"position": [
23536,
16208
],
"parameters": {
"url": "=https://your-crm-api.com/contacts/{{ $json.caller_number }}/bookings",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.1
},
{
"id": "0433e538-eef4-48d8-9511-668bfe41fc27",
"name": "No Response?2",
"type": "n8n-nodes-base.if",
"position": [
23760,
16208
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "no-booking",
"operator": {
"type": "number",
"operation": "equals"
},
"leftValue": "={{ $json.booking_count || 0 }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2
},
{
"id": "5a1e4e37-abf8-4979-b90b-7ba2da1f05e7",
"name": "Send Follow-up SMS2",
"type": "n8n-nodes-base.twilio",
"position": [
23984,
16208
],
"parameters": {
"to": "={{ $json.caller_number }}",
"from": "+1234567890",
"message": "=Hi {{ $json.customer_name }},\n\nWe tried reaching you yesterday. Still interested in scheduling?\n\nBook now: {{ $json.booking_link }}\n\nOr call us: +1-555-0100\n\nThanks!\nYour Business Name",
"options": {}
},
"typeVersion": 1
}
],
"connections": {
"Send SMS2": {
"main": [
[
{
"node": "Merge Results",
"type": "main",
"index": 0
}
]
]
},
"Send SMS?2": {
"main": [
[
{
"node": "Send SMS2",
"type": "main",
"index": 0
}
]
]
},
"Log to CRM2": {
"main": [
[
{
"node": "Notify Sales Team3",
"type": "main",
"index": 0
}
]
]
},
"Send Email2": {
"main": [
[
{
"node": "Merge Results",
"type": "main",
"index": 1
}
]
]
},
"Send Email?2": {
"main": [
[
{
"node": "Send Email2",
"type": "main",
"index": 0
}
]
]
},
"Merge Results": {
"main": [
[
{
"node": "Track Results",
"type": "main",
"index": 0
}
]
]
},
"No Response?2": {
"main": [
[
{
"node": "Send Follow-up SMS2",
"type": "main",
"index": 0
}
]
]
},
"Track Results": {
"main": [
[
{
"node": "Log to CRM2",
"type": "main",
"index": 0
}
]
]
},
"Wait 24 Hours2": {
"main": [
[
{
"node": "Check if Booked2",
"type": "main",
"index": 0
}
]
]
},
"Check if Booked2": {
"main": [
[
{
"node": "No Response?2",
"type": "main",
"index": 0
}
]
]
},
"Prepare Messages2": {
"main": [
[
{
"node": "Send SMS?2",
"type": "main",
"index": 0
},
{
"node": "Send Email?2",
"type": "main",
"index": 0
}
]
]
},
"Notify Sales Team3": {
"main": [
[
{
"node": "Wait 24 Hours2",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model8": {
"ai_languageModel": [
[
{
"node": "Generate AI Response2",
"type": "ai_languageModel",
"index": 0
},
{
"node": "Structured Output Parser8",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Merge Customer Data2": {
"main": [
[
{
"node": "Generate AI Response2",
"type": "main",
"index": 0
}
]
]
},
"Analyze Call Context2": {
"main": [
[
{
"node": "Check Existing Contact2",
"type": "main",
"index": 0
}
]
]
},
"Generate AI Response2": {
"main": [
[
{
"node": "Prepare Messages2",
"type": "main",
"index": 0
}
]
]
},
"Missed Call Detected2": {
"main": [
[
{
"node": "Analyze Call Context2",
"type": "main",
"index": 0
}
]
]
},
"Check Existing Contact2": {
"main": [
[
{
"node": "Merge Customer Data2",
"type": "main",
"index": 0
}
]
]
},
"Structured Output Parser8": {
"ai_outputParser": [
[
{
"node": "Generate AI Response2",
"type": "ai_outputParser",
"index": 0
}
]
]
}
}
}
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.
httpHeaderAuthsmtptwilioApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
• Twilio webhook detects missed/unanswered calls automatically • Analyzes call context (time of day, business hours, weekend/weekday) • Checks CRM for existing customer data and purchase history (optional) • AI Chain (GPT-4o) generates personalized recovery messages for SMS and…
Source: https://n8n.io/workflows/9166/ — 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.
Schedule Twilio. Uses twilioTrigger, lmChatOpenAi, airtable, twilio. Event-driven trigger; 36 nodes.
Schedule Twilio. Uses twilioTrigger, lmChatOpenAi, airtable, twilio. Event-driven trigger; 36 nodes.
This n8n workflow builds an appointment scheduling AI agent which can Take enquiries from prospective customers and help them book an appointment by checking appointment availability Where no appointm
Door-to-door HVAC companies seeking automated lead capture and appointment scheduling.
This n8n workflow orchestrates a powerful suite of AI Agents and automations to manage and optimize various aspects of an e-commerce operation, particularly for platforms like Shopify. It leverages La