Bland AI email verification with BillionVerify
Bland AI is an AI-powered voice calling platform that automates phone outreach at scale. When phone campaigns are backed by email follow-ups or contact lists gathered via email, integrating BillionVerify ensures those email addresses are deliverable before any outreach β voice or written β begins.
Why verify before the send
AI calling campaigns often pair with email sequences for callbacks, summaries, or appointment confirmations. Sending those follow-up emails to invalid addresses causes bounces that damage your domain reputation and leave prospects without the information they need. BillionVerify validates every address before your Bland AI workflows trigger email steps.
Ready-to-use n8n workflow
Import this workflow into n8n β it verifies every address with BillionVerify before Bland AI sends, so only deliverable contacts are emailed. Install the BillionVerify community node first, then add your API key. Adapted from this n8n template
{
"name": "Qualify and call back inbound leads with OpenAI, Bland AI, Airtable and SendGrid + BillionVerify",
"nodes": [
{
"id": "6d51b2a6-28ab-416b-b489-dfbccb3a002c",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
960,
640
],
"webhookId": "LEAD_INTAKE_WEBHOOK_ID",
"parameters": {
"path": "=LEAD_INTAKE_WEBHOOK_ID",
"options": {
"allowedOrigins": "*",
"responseHeaders": {
"entries": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2.1
},
{
"id": "3288c234-0c1b-42fe-8025-8d38f2858963",
"name": "Validate & Clean Data",
"type": "n8n-nodes-base.code",
"position": [
1184,
640
],
"parameters": {
"jsCode": "// Fixed Tally webhook validation using actual structure\nconst webhookData = $input.first().json;\n\nconsole.log(\"=== PROCESSING TALLY WEBHOOK ===\");\n\nlet leadData = {};\n\ntry {\n // Access fields from the correct location: body.data.fields\n const fields = webhookData.body?.data?.fields;\n \n if (!fields || !Array.isArray(fields)) {\n throw new Error(\"No fields array found in webhook.body.data.fields\");\n }\n \n console.log(\"Processing\", fields.length, \"fields from Tally\");\n \n // Process each field\n fields.forEach((field, index) => {\n console.log(`Field ${index}: \"${field.label}\" (${field.type})`);\n \n switch(field.label) {\n case 'Name':\n leadData.name = field.value;\n console.log(`β Name: ${field.value}`);\n break;\n \n case 'Email':\n leadData.email = field.value;\n console.log(`β Email: ${field.value}`);\n break;\n \n case 'Phone Number':\n leadData.phone = field.value;\n console.log(`β Phone: ${field.value}`);\n break;\n \n case 'Company Name':\n leadData.company = field.value;\n console.log(`β Company: ${field.value}`);\n break;\n \n case 'Message':\n leadData.message = field.value;\n console.log(`β Message: ${field.value}`);\n break;\n \n case 'Company Size':\n // For dropdown fields, map the selected ID to text using options\n if (field.type === 'DROPDOWN' && field.options && field.value) {\n const selectedId = Array.isArray(field.value) ? field.value[0] : field.value;\n const selectedOption = field.options.find(opt => opt.id === selectedId);\n leadData.company_size = selectedOption ? selectedOption.text : 'Not specified';\n console.log(`β Company Size: ${selectedId} β ${leadData.company_size}`);\n } else {\n leadData.company_size = field.value || 'Not specified';\n }\n break;\n \n case 'When do you need a solution?':\n if (field.type === 'DROPDOWN' && field.options && field.value) {\n const selectedId = Array.isArray(field.value) ? field.value[0] : field.value;\n const selectedOption = field.options.find(opt => opt.id === selectedId);\n leadData.timeline = selectedOption ? selectedOption.text : 'Not specified';\n console.log(`β Timeline: ${selectedId} β ${leadData.timeline}`);\n } else {\n leadData.timeline = field.value || 'Not specified';\n }\n break;\n \n case \"What's your role in the company?\":\n if (field.type === 'DROPDOWN' && field.options && field.value) {\n const selectedId = Array.isArray(field.value) ? field.value[0] : field.value;\n const selectedOption = field.options.find(opt => opt.id === selectedId);\n leadData.role = selectedOption ? selectedOption.text : 'Not specified';\n console.log(`β Role: ${selectedId} β ${leadData.role}`);\n } else {\n leadData.role = field.value || 'Not specified';\n }\n break;\n \n default:\n console.log(`β οΈ Unmapped field: \"${field.label}\"`);\n }\n });\n \n console.log(\"Final extracted data:\", leadData);\n \n} catch (error) {\n console.log(\"β ERROR:\", error.message);\n return [{\n json: {\n status: \"error\",\n errors: [`Processing failed: ${error.message}`],\n debug: {\n webhookStructure: typeof webhookData,\n hasBody: !!webhookData.body,\n hasBodyData: !!(webhookData.body && webhookData.body.data),\n hasFields: !!(webhookData.body && webhookData.body.data && webhookData.body.data.fields)\n }\n }\n }];\n}\n\n// Validation\nconst errors = [];\nif (!leadData.name || leadData.name.trim() === '') {\n errors.push(\"Name is required\");\n}\nif (!leadData.email || !leadData.email.includes('@')) {\n errors.push(\"Valid email is required\");\n}\nif (!leadData.phone || leadData.phone.trim() === '') {\n errors.push(\"Phone is required\");\n}\n\nif (errors.length > 0) {\n console.log(\"β Validation failed:\", errors);\n return [{\n json: {\n status: \"error\",\n errors: errors,\n debug: { extractedData: leadData }\n }\n }];\n}\n\n// Create final structured lead object\nconst cleanedData = {\n name: leadData.name.trim(),\n email: leadData.email.toLowerCase().trim(),\n phone: leadData.phone.replace(/[^\\d+]/g, ''),\n company: leadData.company?.trim() || \"\",\n message: leadData.message?.trim() || \"\",\n company_size: leadData.company_size,\n timeline: leadData.timeline,\n role: leadData.role,\n timestamp: new Date().toISOString(),\n leadId: `lead_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`\n};\n\nconsole.log(\"β
SUCCESS - Final lead data:\");\nconsole.log(` Company Size: ${cleanedData.company_size}`);\nconsole.log(` Timeline: ${cleanedData.timeline}`);\nconsole.log(` Role: ${cleanedData.role}`);\n\nreturn [{\n json: {\n status: \"success\",\n lead: cleanedData\n }\n}];"
},
"typeVersion": 2
},
{
"id": "31df8d4b-9c41-4d90-8734-bb12512539a4",
"name": "Check Validation Status",
"type": "n8n-nodes-base.if",
"position": [
1760,
640
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "9b310b34-90c0-4f6c-84c5-c28413168ef5",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Validate & Clean Data').item.json.status }}",
"rightValue": "success"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "10d016e3-2314-4c8c-a839-9dde3d2e6d84",
"name": "Create a record",
"type": "n8n-nodes-base.airtable",
"position": [
1984,
544
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "appXXXXXXXXXXXXXX",
"cachedResultUrl": "https://airtable.com/appXXXXXXXXXXXXXX",
"cachedResultName": "Your Lead Base"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblXXXXXXXXXXXXXX",
"cachedResultUrl": "https://airtable.com/appXXXXXXXXXXXXXX/tblXXXXXXXXXXXXXX",
"cachedResultName": "Leads"
},
"columns": {
"value": {
"Name": "={{ $('Validate & Clean Data').item.json.lead.name }}",
"Email": "={{ $('Validate & Clean Data').item.json.lead.email }}",
"Phone": "={{ $('Validate & Clean Data').item.json.lead.phone }}",
"Status": "New",
"Company": "={{ $('Validate & Clean Data').item.json.lead.company }}",
"Message": "={{ $('Validate & Clean Data').item.json.lead.message }}",
"Lead Score": "={{ $('AI Lead Qualification').item.json.message.content.score }}",
"Company Size": "={{ $('Validate & Clean Data').item.json.lead.company_size }}",
"When do you need a solution?": "={{ $('Validate & Clean Data').item.json.lead.timeline }}",
"What's your role in the company?": "={{ $('Validate & Clean Data').item.json.lead.role }}"
},
"schema": [
{
"id": "Name",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Email",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Email",
"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": "Company Size",
"type": "options",
"display": true,
"options": [
{
"name": "1-10 people",
"value": "1-10 people"
},
{
"name": "11-50 people",
"value": "11-50 people"
},
{
"name": "51-200 people",
"value": "51-200 people"
},
{
"name": "201+ people",
"value": "201+ people"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Company Size",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "When do you need a solution?",
"type": "options",
"display": true,
"options": [
{
"name": "Immediately (within 2 weeks)",
"value": "Immediately (within 2 weeks)"
},
{
"name": "Soon (1-3 months)",
"value": "Soon (1-3 months)"
},
{
"name": "This year (3-12 months)",
"value": "This year (3-12 months)"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "When do you need a solution?",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "What's your role in the company?",
"type": "options",
"display": true,
"options": [
{
"name": "Owner/CEO/Founder",
"value": "Owner/CEO/Founder"
},
{
"name": "Manager/Director",
"value": "Manager/Director"
},
{
"name": "Employee/Team Member",
"value": "Employee/Team Member"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "What's your role in the company?",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Message",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Message",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Lead Score",
"type": "number",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Lead Score",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "options",
"display": true,
"options": [
{
"name": "New",
"value": "New"
},
{
"name": "Contacted",
"value": "Contacted"
},
{
"name": "Qualified",
"value": "Qualified"
},
{
"name": "Unqualified",
"value": "Unqualified"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Meeting Scheduled",
"type": "options",
"display": true,
"options": [
{
"name": "Yes",
"value": "Yes"
},
{
"name": "No",
"value": "No"
},
{
"name": "Pending",
"value": "Pending"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Meeting Scheduled",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Meeting Date",
"type": "dateTime",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Meeting Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Notes",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Notes",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Created",
"type": "string",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "Created",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Lead ID",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Lead ID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Record ID",
"type": "string",
"display": true,
"removed": false,
"readOnly": true,
"required": false,
"displayName": "Record ID",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "create"
},
"credentials": {
"airtableTokenApi": {
"id": "credential-id",
"name": "Airtable Personal Access Token account 4"
}
},
"typeVersion": 2.1
},
{
"id": "00266787-e974-4f60-a81a-d762a959fc13",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1984,
736
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={\n \"success\": false,\n \"message\": \"Validation failed\",\n \"errors\": \"Invalid data provided\"\n}"
},
"typeVersion": 1.4
},
{
"id": "d71fa900-789d-40b3-b3fa-f8fc752ee2ed",
"name": "AI Lead Qualification",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
1408,
640
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-3.5-turbo",
"cachedResultName": "GPT-3.5-TURBO"
},
"options": {},
"messages": {
"values": [
{
"role": "system",
"content": "=You are an expert B2B lead qualification specialist for AI automation services. You now have structured qualification data that is MORE RELIABLE than message analysis. Score leads 1-10 using this weighted scoring system:\n\n**PRIMARY SCORING FACTORS (70% of score):**\n\n**COMPANY SIZE SCORING:**\n- \"201+ people\" = 4 points (Enterprise - high budget, complex processes)\n- \"51-200 people\" = 3 points (Mid-market - growth stage, automation needs)\n- \"11-50 people\" = 2 points (SMB - emerging automation needs)\n- \"1-10 people\" = 1 point (Startup/small - limited budget)\n- Not specified = 0 points\n\n**TIMELINE SCORING:**\n- \"Immediately (within 2 weeks)\" = 3 points (Hot lead - urgent need)\n- \"Soon (1-3 months)\" = 2 points (Warm lead - active buying process)\n- \"This year (3-12 months)\" = 1 point (Planning phase)\n- \"Just exploring\" or not specified = 0 points\n\n**ROLE SCORING:**\n- \"Owner/CEO/Founder\" = 2 points (Decision maker)\n- \"Manager/Director\" = 1.5 points (Strong influence)\n- \"Employee/Team Member\" = 0.5 points (Limited authority)\n- Not specified = 0 points\n\n**SECONDARY FACTORS (30% of score):**\n- Professional email domain (+0.5 points)\n- Complete contact info (+0.5 points)\n- Detailed message with specific pain points (+1 point)\n- Business email vs personal (-0.5 for gmail/yahoo unless startup)\n\n**TOTAL SCORING SYSTEM:**\n- 8.5-10 points = SCORE 9-10 (HOT - Immediate call)\n- 6.5-8.4 points = SCORE 7-8 (WARM - Priority email + call within 24h)\n- 4.5-6.4 points = SCORE 5-6 (LUKEWARM - Nurture sequence)\n- Below 4.5 points = SCORE 1-4 (COLD - Minimal follow-up)\n\n**EXAMPLES:**\n- 201+ people + Immediately + Owner/CEO = 9 points = HOT LEAD\n- 51-200 people + Soon + Manager + good message = 7.5 points = WARM LEAD\n- 11-50 people + This year + Employee = 4 points = LUKEWARM\n- 1-10 people + Just exploring = 2 points = COLD\n\nIMPORTANT: Even if someone writes a brief message like 'Tell me more', if they selected '201+ people' + 'Immediately' + 'Owner/CEO', that's a 9-point HOT LEAD. The structured data is more reliable than message analysis.\n\nReturn JSON: {\"score\": X, \"priority\": \"hot/warm/lukewarm/cold\", \"reasoning\": \"specific point breakdown\", \"next_action\": \"immediate_call/priority_email/nurture_sequence/minimal_followup\", \"deal_size_estimate\": \"enterprise/mid-market/small/micro\"}"
},
{
"content": "=LEAD QUALIFICATION ANALYSIS\n\n**STRUCTURED DATA:**\n- Company Size: {{ $node['Validate & Clean Data'].json.lead.company_size }}\n- Timeline: {{ $node['Validate & Clean Data'].json.lead.timeline }}\n- Role: {{ $node['Validate & Clean Data'].json.lead.role }}\n\n**CONTACT DETAILS:**\n- Name: {{ $node['Validate & Clean Data'].json.lead.name }}\n- Email: {{ $node['Validate & Clean Data'].json.lead.email }}\n- Company: {{ $node['Validate & Clean Data'].json.lead.company }}\n- Phone: {{ $node['Validate & Clean Data'].json.lead.phone }}\n\n**MESSAGE:**\n{{ $node['Validate & Clean Data'].json.lead.message }}\n\n**SCORING INSTRUCTIONS:**\nUse the point system above. Calculate total points from company size + timeline + role + secondary factors, then map to 1-10 score. Provide detailed reasoning showing your point calculations."
}
]
},
"jsonOutput": true
},
"credentials": {
"openAiApi": {
"id": "credential-id",
"name": "OpenAi account"
}
},
"typeVersion": 1.8
},
{
"id": "1c325fd0-be68-4a0c-b4dd-df0cdb4c9fa7",
"name": "Check for Follow-Up Sequence",
"type": "n8n-nodes-base.if",
"position": [
3248,
640
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "70420fe4-f926-4afd-9d95-bbaa859f2ff5",
"operator": {
"type": "number",
"operation": "lte"
},
"leftValue": "={{ $('AI Lead Qualification').item.json.message.content.score }}",
"rightValue": 6
}
]
}
},
"typeVersion": 2.2
},
{
"id": "6f7725c3-6fe5-41f8-b60a-29a7e465da4a",
"name": "Send Nurture Email",
"type": "n8n-nodes-base.sendGrid",
"position": [
3472,
640
],
"parameters": {
"subject": "={{ $node[\"Validate & Clean Data\"].json.lead.name }}, here's how AI automation can help {{ $node[\"Validate & Clean Data\"].json.lead.company }}",
"toEmail": "={{ $node[\"Validate & Clean Data\"].json.lead.email }}",
"fromName": "Your Name - AI Automation",
"resource": "mail",
"fromEmail": "user@example.com",
"contentType": "text/html",
"contentValue": "=<p>Hi {{ $node[\"Validate & Clean Data\"].json.lead.name }},</p> <p>Thanks for your interest in AI automation! I wanted to share some insights that might help {{ $node[\"Validate & Clean Data\"].json.lead.company }}.</p> <p><strong>Companies like yours typically see results in:</strong></p> <ul> <li>40-60% reduction in manual data processing</li> <li>Automated lead qualification and routing</li> <li>Improved customer response times</li> <li>Enhanced data accuracy and insights</li> </ul> <p><strong>Free Resources:</strong></p> <p>I've prepared a case study showing how a similar company automated their lead process and increased conversion by 35%.</p> <p>Would you like me to send it along or would you like a brief call to discuss your specific automation opportunities?</p> <p>Best regards,<br>Abi</p>",
"additionalFields": {}
},
"credentials": {
"sendGridApi": {
"id": "credential-id",
"name": "SendGrid account"
}
},
"typeVersion": 1
},
{
"id": "48cf115e-b84a-4b36-97fe-2235045e3c4e",
"name": "Check for AI Calling",
"type": "n8n-nodes-base.if",
"position": [
2208,
544
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a468a91b-7678-4609-8546-6ec3741b5728",
"operator": {
"type": "number",
"operation": "gte"
},
"leftValue": "={{ $('AI Lead Qualification').item.json.message.content.score }}",
"rightValue": 8
}
]
}
},
"typeVersion": 2.2
},
{
"id": "9aafc4e1-88d5-4d2e-807b-e90fc8fcab2b",
"name": "Trigger AI Phone Call",
"type": "n8n-nodes-base.httpRequest",
"position": [
3248,
448
],
"parameters": {
"url": "https://api.bland.ai/v1/calls",
"method": "POST",
"options": {},
"jsonBody": "= {\n \"phone_number\": \"{{ $node['Validate & Clean Data'].json.lead.phone }}\",\n \"task\": \"You are Sarah, an AI automation consultant. You are calling {{ $node['Validate & Clean Data'].json.lead.name }} from {{ $node['Validate & Clean Data'].json.lead.company }}. Start by saying: 'Hi, is this {{ $node['Validate & Clean Data'].json.lead.name }}?' Wait for confirmation. Then introduce yourself: 'Hi {{ $node['Validate & Clean Data'].json.lead.name }}, this is Sarah from [YOUR_COMPANY]. I'm calling about the AI automation inquiry you submitted for {{ $node['Validate & Clean Data'].json.lead.company }}. Do you have a quick minute to chat?' If they're available, ask: 'What prompted you to look into AI automation for your business?' Listen to their response, then ask about their current challenges. If they show genuine interest in learning more, say: 'I'd love to schedule a brief 15-minute consultation to discuss how we could help {{ $node['Validate & Clean Data'].json.lead.company }} specifically. {{ $node['Code in JavaScript'].json.availabilityDescription }}. What day and time would work best for you?' Once they give a preferred time, repeat it back: 'Perfect, so that's [repeat their preferred day and time]. I'll have someone send you the calendar invite for [repeat exact day/time]. Thanks so much for your time today!'\",\n \"voice\": \"maya\",\n \"max_duration\": 300,\n \"webhook\": \"https://YOUR-N8N-INSTANCE.com/webhook/CALL_OUTCOME_WEBHOOK_ID\",\n \"metadata\": {\n\"record_id\": \"{{ $node['Create a record'].json.id }}\",\n \"lead_name\": \"{{ $node['Validate & Clean Data'].json.lead.name }}\",\n \"lead_email\": \"{{ $node['Validate & Clean Data'].json.lead.email }}\",\n \"lead_id\": \"{{ $node['Validate & Clean Data'].json.lead.leadId }}\",\n \"lead_company\": \"{{ $node['Validate & Clean Data'].json.lead.company }}\",\n\"message\": \"{{ $node['Validate & Clean Data'].json.lead.message }}\"\n }\n }\n",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_TOKEN_HERE"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "d508fb3f-5407-46d2-b402-f48207349a09",
"name": "Send Priority Email",
"type": "n8n-nodes-base.sendGrid",
"position": [
2432,
640
],
"parameters": {
"subject": "=Hi {{ $node[\"Validate & Clean Data\"].json.lead.name }}, let's discuss your AI automation needs",
"toEmail": "={{ $('Validate & Clean Data').item.json.lead.email }}",
"fromName": "Your Name - AI Automation",
"resource": "mail",
"fromEmail": "user@example.com",
"contentType": "text/html",
"contentValue": "=<p>Hi {{ $node[\"Validate & Clean Data\"].json.lead.name }},</p>\n\n<p>Thanks for your interest in AI automation! Based on your message, I can see {{ $node[\"Validate & Clean Data\"].json.lead.company }} has strong automation potential.</p>\n\n<p> I wanted to reach out personally and I'd love to schedule a 15-minute call to discuss:</p>\n<ul>\n<li>Your specific automation challenges</li>\n<li>How AI can streamline your processes</li>\n<li>ROI potential for your business</li>\n</ul>\n\n<p>When would be a good time this week?</p>\n\n<p>Best regards,<br>\nAbi<br>\nAI Automation Specialist</p>",
"additionalFields": {}
},
"credentials": {
"sendGridApi": {
"id": "credential-id",
"name": "SendGrid account"
}
},
"typeVersion": 1
},
{
"id": "4b33e334-7e42-4c99-9026-899e8125f0f9",
"name": "Call Outcome Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
960,
1408
],
"webhookId": "CALL_OUTCOME_WEBHOOK_ID",
"parameters": {
"path": "CALL_OUTCOME_WEBHOOK_ID",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2.1
},
{
"id": "40962417-a8de-49f4-bee7-29c33eb7a266",
"name": "Get availability in a calendar",
"type": "n8n-nodes-base.googleCalendar",
"position": [
2432,
448
],
"parameters": {
"options": {},
"timeMax": "={{ $now.plus(7, 'day') }}",
"calendar": {
"__rl": true,
"mode": "list",
"value": "user@example.com",
"cachedResultName": "user@example.com"
},
"resource": "calendar"
},
"credentials": {
"googleCalendarOAuth2Api": {
"id": "credential-id",
"name": "Google Calendar account"
}
},
"typeVersion": 1.3
},
{
"id": "843172bc-73f7-451d-ba31-796f7c7d99da",
"name": "Code in JavaScript",
"type": "n8n-nodes-base.code",
"position": [
3024,
448
],
"parameters": {
"jsCode": "// Get calendar events - handle different data structures\nlet events = [];\nconst inputData = $input.all()[0];\n\n// Handle different possible data structures from Google Calendar\nif (inputData && inputData.json) {\n if (Array.isArray(inputData.json)) {\n events = inputData.json;\n } else if (inputData.json.items && Array.isArray(inputData.json.items)) {\n events = inputData.json.items;\n } else if (inputData.json.events && Array.isArray(inputData.json.events)) {\n events = inputData.json.events;\n }\n}\n\nconsole.log(\"Events data:\", events);\n\nconst now = new Date();\n\n// Your working hours\nconst workingHours = {\n start: 9, // 9 AM\n end: 17, // 5 PM\n days: [1, 2, 3, 4, 5], // Monday-Friday\n duration: 15 // 15-minute meetings\n};\n\n// Find all available days and general time ranges\nconst availableDays = [];\nconst specificSlots = [];\n\nfor (let day = 1; day <= 7; day++) {\n const checkDate = new Date(now.getTime() + day * 24 * 60 * 60 * 1000);\n \n if (workingHours.days.includes(checkDate.getDay())) {\n let dayHasAvailability = false;\n let availableHours = [];\n \n for (let hour = workingHours.start; hour < workingHours.end; hour++) {\n const slotTime = new Date(checkDate);\n slotTime.setHours(hour, 0, 0, 0);\n \n // Check if slot conflicts with existing events\n const hasConflict = events.length > 0 && events.some(event => {\n if (!event.start || !event.end) return false;\n const eventStart = new Date(event.start.dateTime || event.start.date);\n const eventEnd = new Date(event.end.dateTime || event.end.date);\n return slotTime >= eventStart && slotTime < eventEnd;\n });\n \n if (!hasConflict && slotTime > now) {\n dayHasAvailability = true;\n availableHours.push(hour);\n \n // Keep some specific slots for calendar booking later\n if (specificSlots.length < 5) {\n specificSlots.push(slotTime.toISOString());\n }\n }\n }\n \n if (dayHasAvailability) {\n const dayName = checkDate.toLocaleDateString('en-US', { weekday: 'long' });\n const formattedDate = checkDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });\n \n // Create time ranges instead of specific times\n const timeRanges = [];\n if (availableHours.includes(9) || availableHours.includes(10) || availableHours.includes(11)) {\n timeRanges.push('morning (9am-12pm)');\n }\n if (availableHours.includes(13) || availableHours.includes(14) || availableHours.includes(15)) {\n timeRanges.push('afternoon (1pm-4pm)');\n }\n if (availableHours.includes(16)) {\n timeRanges.push('late afternoon (4pm-5pm)');\n }\n \n if (timeRanges.length > 0) {\n const dayDescription = dayName + ' ' + formattedDate + ' - ' + timeRanges.join(' or ');\n availableDays.push(dayDescription);\n }\n }\n }\n \n if (availableDays.length >= 4) break; // Limit to 4 days of availability\n}\n\n// Create flexible availability description\nconst availabilityDescription = availableDays.length > 0 \n ? 'I have good availability: ' + availableDays.join(', ')\n : 'I have flexibility this week between 9am-5pm Monday through Friday';\n\nreturn [{ \n json: {\n availabilityDescription: availabilityDescription, // For AI script\n availableDateTime: specificSlots[0], // First specific slot for calendar booking\n allAvailableSlots: specificSlots, // All specific slots available\n debug: { totalEvents: events.length, availableDays: availableDays.length }\n }\n}];"
},
"typeVersion": 2
},
{
"id": "71f8e9d7-72f7-4570-9058-8071839b89ab",
"name": "Parse Call Outcome",
"type": "n8n-nodes-base.code",
"position": [
1184,
1312
],
"parameters": {
"jsCode": "/**\n * Parse Bland.ai webhook β normalize call outcome + surface Airtable record id\n * Works whether Bland posts `variables.metadata` or `metadata` at root.\n */\n\n//// 1) Load payload robustly\nconst fromWebhook = $items('Call Outcome Webhook', 0, 0)?.json;\nconst webhookData = (fromWebhook && Object.keys(fromWebhook).length)\n ? fromWebhook\n : ($input.first()?.json ?? {});\n\nif (!webhookData || Object.keys(webhookData).length === 0) {\n console.log('Parse Call Outcome: empty payload (no webhook item, no input). Skipping.');\n return $input.all().length ? $input.all() : [{ json: { skipped: true } }];\n}\n\nconst body = webhookData.body ?? webhookData;\n\n//// 2) Transcript (array or single field)\nlet transcriptText = '';\nif (Array.isArray(body.transcripts)) {\n transcriptText = body.transcripts\n .map(t => (t && t.text ? String(t.text) : ''))\n .filter(Boolean)\n .join(' ');\n} else if (body.transcript) {\n transcriptText = String(body.transcript);\n} else if (body.summary) {\n transcriptText = String(body.summary);\n}\n\n//// 3) Duration & disposition\nconst disposition = String(body.disposition_tag ?? '').toUpperCase();\nconst durationSec = Number(\n body.corrected_duration ??\n body.duration ??\n body.call_length ??\n webhookData.corrected_duration ??\n webhookData.duration ??\n webhookData.call_length ??\n 0\n);\n\n// βConnectedβ = actual human exchange: >5s OR any transcript captured\nconst connected = (durationSec > 5) || (transcriptText.trim().length > 0);\n\n//// 4) Lead basics\nconst vars = body.variables ?? {};\nconst meta = vars.metadata ?? body.metadata ?? {};\nconst leadData = {\n name: body.lead_name ?? meta.lead_name ?? webhookData.lead_name ?? 'Lead',\n email: body.lead_email ?? meta.lead_email ?? webhookData.lead_email ?? '',\n leadId: body.lead_id ?? meta.lead_id ?? webhookData.lead_id ?? '',\n company: body.lead_company ?? meta.lead_company ?? body.company ?? webhookData.lead_company ?? ''\n};\n\n// >>> 5) **Airtable Record ID** from metadata (this is what youβll match on later)\nconst recordId = meta.record_id || webhookData.record_id || '';\n\n//// 6) Interest heuristic\nconst tLower = transcriptText.toLowerCase();\nlet interested =\n ['yes','sure','schedule','book','sounds good',\"let's do\",'lets do','ok','okay','that works']\n .some(k => tLower.includes(k));\n\nif (!interested && ['CALL_BACK_SCHEDULED','MEETING_SCHEDULED','FOLLOW_UP'].includes(disposition)) {\n interested = true;\n}\n\n//// 7) Extract an agreed time phrase (very light NLP)\nlet agreedTime = null;\nlet agreedDateTime = null;\n\nconst ampm = '(?:am|a\\\\.m\\\\.|pm|p\\\\.m\\\\.)';\nconst patterns = [\n // friday at 11am\n new RegExp(`(?:monday|tuesday|wednesday|thursday|friday|saturday|sunday)\\\\s+at\\\\s+(\\\\d{1,2}(?::\\\\d{2})?\\\\s*${ampm})`, 'gi'),\n // 11am on friday\n new RegExp(`(\\\\d{1,2}(?::\\\\d{2})?\\\\s*${ampm})\\\\s+(?:on\\\\s+)?(?:monday|tuesday|wednesday|thursday|friday|saturday|sunday)`, 'gi'),\n // tomorrow at 3pm / next week at 2pm\n new RegExp(`(?:tomorrow|next\\\\s+\\\\w+)\\\\s+at\\\\s+(\\\\d{1,2}(?::\\\\d{2})?\\\\s*${ampm})`, 'gi'),\n // friday at eleven a.m.\n new RegExp(`(?:monday|tuesday|wednesday|thursday|friday|saturday|sunday)\\\\s+at\\\\s+([a-z\\\\-]+\\\\s*${ampm})`, 'gi')\n];\n\nfor (const rx of patterns) {\n const m = tLower.match(rx);\n if (m) { agreedTime = m[0]; break; }\n}\n\n//// 8) Coarse normalization to an ISO datetime (fallbacks if needed)\nif (agreedTime) {\n const now = new Date();\n const scheduled = new Date(now.getTime() + 24 * 60 * 60 * 1000); // default = tomorrow\n\n const norm = agreedTime.replace(/\\./g, ''); // normalize a.m./p.m. dots away\n const slots = [\n { key: /\\b10\\s?:?00?\\s?am\\b/, h: 10 },\n { key: /\\beleven\\s?am\\b|\\b11\\s?:?00?\\s?am\\b/, h: 11 },\n { key: /\\b2\\s?:?00?\\s?pm\\b|\\btwo\\s?pm\\b/, h: 14 },\n { key: /\\b3\\s?:?00?\\s?pm\\b|\\bthree\\s?pm\\b/, h: 15 },\n ];\n let setHour = false;\n for (const s of slots) {\n if (s.key.test(norm)) { scheduled.setHours(s.h, 0, 0, 0); setHour = true; break; }\n }\n if (!setHour) {\n if (/\\bpm\\b/.test(norm)) scheduled.setHours(14, 0, 0, 0);\n else scheduled.setHours(10, 0, 0, 0);\n }\n agreedDateTime = scheduled.toISOString();\n}\n\n//// 9) Return normalized payload (includes record_id)\nreturn [{\n json: {\n call_connected: connected,\n call_duration: durationSec || 0,\n lead_interested: interested,\n agreed_time_text: agreedTime,\n agreed_datetime: agreedDateTime,\n transcript: transcriptText,\n\n lead_name: leadData.name,\n lead_email: leadData.email,\n lead_id: leadData.leadId, // optional, unused if you only match by record id\n lead_company: leadData.company,\n\n record_id: recordId, // <<< use this in Airtable updates\n disposition_tag: disposition,\n webhook_data: webhookData\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "47073473-14ff-419b-a41d-6e892ed444ca",
"name": "Check Call Success",
"type": "n8n-nodes-base.if",
"position": [
1632,
1312
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "89d7bb5c-55a5-42a1-bfb7-bc9783256c0f",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.call_success }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.2
},
{
"id": "6c33f06d-642a-4302-966b-0d90272debfe",
"name": "Create Calendar event",
"type": "n8n-nodes-base.googleCalendar",
"position": [
2080,
1216
],
"parameters": {
"end": "={{ $json.end_iso }}",
"start": "={{ $json.start_iso }}",
"calendar": {
"__rl": true,
"mode": "list",
"value": "user@example.com",
"cachedResultName": "user@example.com"
},
"additionalFields": {
"summary": "=AI Automation Consultation - {{ $('Call Outcome Webhook').item.json.lead_name || 'Lead' }}"
}
},
"credentials": {
"googleCalendarOAuth2Api": {
"id": "credential-id",
"name": "Google Calendar account"
}
},
"typeVersion": 1.3
},
{
"id": "810ec7c9-77d6-4452-8e90-dc9464a25ede",
"name": "Add Successful Call Record",
"type": "n8n-nodes-base.airtable",
"position": [
2304,
1216
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "appXXXXXXXXXXXXXX",
"cachedResultUrl": "https://airtable.com/appXXXXXXXXXXXXXX",
"cachedResultName": "Your Lead Base"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblXXXXXXXXXXXXXX",
"cachedResultUrl": "https://airtable.com/appXXXXXXXXXXXXXX/tblXXXXXXXXXXXXXX",
"cachedResultName": "Leads"
},
"columns": {
"value": {
"Notes": "Call successful - Meeting scheduled for {{ $('Parse Call Outcome').item.json.agreed_time_text }}",
"Status": "Contacted",
"Message": "={{ $json.lead_message || $json.message_or_summary || '' }}\n",
"Meeting Date": "={{\n (() => {\n const fromCal = $items('Create Calendar event', 0, 0).json.start?.dateTime;\n const fromParse = $('Parse Call Outcome').item.json.agreed_datetime;\n const d = fromCal || fromParse;\n return d ? new Date(d).toISOString().slice(0,10) : null; // YYYY-MM-DD\n })()\n}}\n",
"Meeting Scheduled": "Yes"
},
"schema": [
{
"id": "id",
"type": "string",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "id",
"defaultMatch": true
},
{
"id": "Name",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Email",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Phone",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Company",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Company",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Company Size",
"type": "options",
"display": true,
"options": [
{
"name": "1-10 people",
"value": "1-10 people"
},
{
"name": "11-50 people",
"value": "11-50 people"
},
{
"name": "51-200 people",
"value": "51-200 people"
},
{
"name": "201+ people",
"value": "201+ people"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Company Size",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "When do you need a solution?",
"type": "options",
"display": true,
"options": [
{
"name": "Immediately (within 2 weeks)",
"value": "Immediately (within 2 weeks)"
},
{
"name": "Soon (1-3 months)",
"value": "Soon (1-3 months)"
},
{
"name": "This year (3-12 months)",
"value": "This year (3-12 months)"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "When do you need a solution?",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "What's your role in the company?",
"type": "options",
"display": true,
"options": [
{
"name": "Owner/CEO/Founder",
"value": "Owner/CEO/Founder"
},
{
"name": "Manager/Director",
"value": "Manager/Director"
},
{
"name": "Employee/Team Member",
"value": "Employee/Team Member"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "What's your role in the company?",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Message",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Message",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Lead Score",
"type": "number",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Lead Score",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "options",
"display": true,
"options": [
{
"name": "New",
"value": "New"
},
{
"name": "Contacted",
"value": "Contacted"
},
{
"name": "Qualified",
"value": "Qualified"
},
{
"name": "Unqualified",
"value": "Unqualified"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Meeting Scheduled",
"type": "options",
"display": true,
"options": [
{
"name": "Yes",
"value": "Yes"
},
{
"name": "No",
"value": "No"
},
{
"name": "Pending",
"value": "Pending"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Meeting Scheduled",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Meeting Date",
"type": "dateTime",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Meeting Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Notes",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Notes",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Created",
"type": "string",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "Created",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Lead ID",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Lead ID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Record ID",
"type": "string",
"display": true,
"removed": false,
"readOnly": true,
"required": false,
"displayName": "Record ID",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {
"typecast": true
},
"operation": "update"
},
"credentials": {
"airtableTokenApi": {
"id": "credential-id",
"name": "Airtable Personal Access Token account 4"
}
},
"typeVersion": 2.1
},
{
"id": "9aecb6e7-0af0-41a0-9be6-98c4c8379228",
"name": "Send Booking ConfirmationEmail",
"type": "n8n-nodes-base.sendGrid",
"position": [
2528,
1216
],
"parameters": {
"subject": "Your AI automation consultation is confirmed!",
"toEmail": "={{ \n String(\n $json.fields?.Email \n || $('Parse Call Outcome').item.json.lead_email \n || $items('Call Outcome Webhook',0,0).json.body?.variables?.metadata?.lead_email \n || ''\n ).trim()\n}}\n",
"fromName": "Your Name - AI Automation",
"resource": "mail",
"fromEmail": "user@example.com",
"contentType": "text/html",
"contentValue": "=Hi {{ \n $json.fields?.Name \n || $('Parse Call Outcome').item.json.lead_name \n || 'there'\n}}, Great talking with you today! Your 15-minute AI automation consultation is confirmed for {{ $('Parse Call Outcome').item.json.agreed_time_text }}\n{{ (() => {\n const ev = $('Create Calendar event').item.json;\n if (!ev?.start?.dateTime) return ''; // nothing to add\n const tz = ev.start.timeZone || 'UTC';\n const pretty = new Date(ev.start.dateTime).toLocaleString('en-GB', {\n weekday: 'long', day: 'numeric', month: 'short', year: 'numeric',\n hour: 'numeric', minute: '2-digit', timeZone: tz\n });\n return `, ${pretty}`; // prepend the comma only if we have a value\n})() }}\n. I've sent you a calendar invite. Looking forward to our conversation!",
"additionalFields": {}
},
"credentials": {
"sendGridApi": {
"id": "credential-id",
"name": "SendGrid account"
}
},
"typeVersion": 1
},
{
"id": "0e48d014-b418-4040-a4a6-17b567a2906f",
"name": "Update Call Record",
"type": "n8n-nodes-base.airtable",
"position": [
3024,
640
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "appXXXXXXXXXXXXXX",
"cachedResultUrl": "https://airtable.com/appXXXXXXXXXXXXXX",
"cachedResultName": "Your Lead Base"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblXXXXXXXXXXXXXX",
"cachedResultUrl": "https://airtable.com/appXXXXXXXXXXXXXX/tblXXXXXXXXXXXXXX",
"cachedResultName": "Leads"
},
"columns": {
"value": {
"Notes": "Call attempted - Email sent as follow-up",
"Status": "Contacted",
"Message": "={{ $('Create a record').item.json.fields.Message }}",
"Record ID": "={{ $('Create a record').item.json.fields['Record ID'] }}",
"Lead Score": 0,
"Meeting Scheduled": "No"
},
"schema": [
{
"id": "id",
"type": "string",
"display": true,
"removed": true,
"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": "Email",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Email",
"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": "Company Size",
"type": "options",
"display": true,
"options": [
{
"name": "1-10 people",
"value": "1-10 people"
},
{
"name": "11-50 people",
"value": "11-50 people"
},
{
"name": "51-200 people",
"value": "51-200 people"
},
{
"name": "201+ people",
"value": "201+ people"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Company Size",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "When do you need a solution?",
"type": "options",
"display": true,
"options": [
{
"name": "Immediately (within 2 weeks)",
"value": "Immediately (within 2 weeks)"
},
{
"name": "Soon (1-3 months)",
"value": "Soon (1-3 months)"
},
{
"name": "This year (3-12 months)",
"value": "This year (3-12 months)"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "When do you need a solution?",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "What's your role in the company?",
"type": "options",
"display": true,
"options": [
{
"name": "Owner/CEO/Founder",
"value": "Owner/CEO/Founder"
},
{
"name": "Manager/Director",
"value": "Manager/Director"
},
{
"name": "Employee/Team Member",
"value": "Employee/Team Member"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "What's your role in the company?",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Message",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Message",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Lead Score",
"type": "number",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Lead Score",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "options",
"display": true,
"options": [
{
"name": "New",
"value": "New"
},
{
"name": "Contacted",
"value": "Contacted"
},
{
"name": "Qualified",
"value": "Qualified"
},
{
"name": "Unqualified",
"value": "Unqualified"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Meeting Scheduled",
"type": "options",
"display": true,
"options": [
{
"name": "Yes",
"value": "Yes"
},
{
"name": "No",
"value": "No"
},
{
"name": "Pending",
"value": "Pending"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Meeting Scheduled",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Meeting Date",
"type": "dateTime",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Meeting Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Notes",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Notes",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Created",
"type": "string",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "Created",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Lead ID",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Lead ID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Record ID",
"type": "string",
"display": true,
"removed": false,
"readOnly": true,
"required": false,
"displayName": "Record ID",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"Record ID"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update"
},
"credentials": {
"airtableTokenApi": {
"id": "credential-id",
"name": "Airtable Personal Access Token account 4"
}
},
"typeVersion": 2.1
},
{
"id": "5afcd2d8-fa53-45da-b66f-b990cfca279e",
"name": "Send Priority Email for Failed Calls",
"type": "n8n-nodes-base.sendGrid",
"position": [
1856,
1408
],
"parameters": {
"subject": "={{ $('Parse Call Outcome').item.json.lead_name }}, I tried calling about your AI automation inquiry",
"toEmail": "={{ \n String(\n $json.fields?.Email \n || $('Parse Call Outcome').item.json.lead_email \n || $items('Call Outcome Webhook',0,0).json.body?.variables?.metadata?.lead_email \n || ''\n ).trim()\n}}\n",
"fromName": "Your Name - AI Automation",
"resource": "mail",
"fromEmail": "user@example.com",
"contentType": "text/html",
"contentValue": "={{ \n $json.fields?.Name \n || $('Parse Call Outcome').item.json.lead_name \n || 'there'\n}},</p>\n\n<p>I just tried calling you about the AI automation inquiry you submitted for {{ $('Parse Call Outcome').item.json.lead_name }}. Unfortunately, I wasn't able to reach you.</p>\n\n<p>Based on your inquiry details, I can see {{ $('Parse Call Outcome').item.json.lead_name }} has strong automation potential, so I wanted to follow up personally.</p>\n\n<p><strong>I'd love to schedule a quick 15-minute call to discuss:</strong></p>\n<ul>\n<li>Your specific automation challenges</li>\n<li>How AI can streamline your processes</li>\n<li>ROI potential for your business size</li>\n</ul>\n\n<p>You can reply to this email with a few times that work for you this week, or feel free to call me back at your convenience.</p>\n\n<p>Best regards,<br>\nAbi<br>\nAI Automation Specialist<br>\nyour-email@example.com</p>",
"additionalFields": {}
},
"credentials": {
"sendGridApi": {
"id": "credential-id",
"name": "SendGrid account"
}
},
"typeVersion": 1
},
{
"id": "1da629d5-aa5e-45cf-abae-ba9d0b00fffe",
"name": "Update Failed Call Record",
"type": "n8n-nodes-base.airtable",
"position": [
2080,
1408
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "appXXXXXXXXXXXXXX",
"cachedResultUrl": "https://airtable.com/appXXXXXXXXXXXXXX",
"cachedResultName": "Your Lead Base"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblXXXXXXXXXXXXXX",
"cachedResultUrl": "https://airtable.com/appXXXXXXXXXXXXXX/tblXXXXXXXXXXXXXX",
"cachedResultName": "Leads"
},
"columns": {
"value": {
"Notes": "Call attempted - Email sent as follow-up",
"Status": "Contacted",
"Record ID": "{{ $json.record_id }}",
"Lead Score": 0,
"Meeting Scheduled": "No"
},
"schema": [
{
"id": "id",
"type": "string",
"display": true,
"removed": true,
"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": "Email",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Email",
"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": "Company Size",
"type": "options",
"display": true,
"options": [
{
"name": "1-10 people",
"value": "1-10 people"
},
{
"name": "11-50 people",
"value": "11-50 people"
},
{
"name": "51-200 people",
"value": "51-200 people"
},
{
"name": "201+ people",
"value": "201+ people"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Company Size",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "When do you need a solution?",
"type": "options",
"display": true,
"options": [
{
"name": "Immediately (within 2 weeks)",
"value": "Immediately (within 2 weeks)"
},
{
"name": "Soon (1-3 months)",
"value": "Soon (1-3 months)"
},
{
"name": "This year (3-12 months)",
"value": "This year (3-12 months)"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "When do you need a solution?",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "What's your role in the company?",
"type": "options",
"display": true,
"options": [
{
"name": "Owner/CEO/Founder",
"value": "Owner/CEO/Founder"
},
{
"name": "Manager/Director",
"value": "Manager/Director"
},
{
"name": "Employee/Team Member",
"value": "Employee/Team Member"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "What's your role in the company?",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Message",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Message",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Lead Score",
"type": "number",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Lead Score",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "options",
"display": true,
"options": [
{
"name": "New",
"value": "New"
},
{
"name": "Contacted",
"value": "Contacted"
},
{
"name": "Qualified",
"value": "Qualified"
},
{
"name": "Unqualified",
"value": "Unqualified"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Meeting Scheduled",
"type": "options",
"display": true,
"options": [
{
"name": "Yes",
"value": "Yes"
},
{
"name": "No",
"value": "No"
},
{
"name": "Pending",
"value": "Pending"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Meeting Scheduled",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Meeting Date",
"type": "dateTime",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Meeting Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Notes",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Notes",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Created",
"type": "string",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "Created",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Lead ID",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Lead ID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Record ID",
"type": "string",
"display": true,
"removed": false,
"readOnly": true,
"required": false,
"displayName": "Record ID",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update"
},
"credentials": {
"airtableTokenApi": {
"id": "credential-id",
"name": "Airtable Personal Access Token account 4"
}
},
"typeVersion": 2.1
},
{
"id": "119586d0-7a75-4193-90cb-2cf211424d96",
"name": "Respond to Webhook1",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1184,
1504
],
"parameters": {
"options": {
"responseCode": 200
},
"respondWith": "json",
"responseBody": "={\n \"success\": true\n}"
},
"typeVersion": 1.4
},
{
"id": "383cf3b1-a428-4130-8d8a-57e4d06c8ce4",
"name": "Code",
"type": "n8n-nodes-base.code",
"position": [
1408,
1312
],
"parameters": {
"jsCode": "// Build a single truthy flag the IF node can use\nconst ok =\n $json.call_connected === true &&\n ($json.lead_interested === true || ($json.agreed_datetime ?? null) !== null);\n\n// Optional debug (shows in execution logs)\nconsole.log({\n call_connected: $json.call_connected,\n lead_interested: $json.lead_interested,\n agreed_datetime: $json.agreed_datetime,\n call_success: ok,\n});\n\nreturn [{ json: { ...$json, call_success: ok } }];\n"
},
"typeVersion": 2
},
{
"id": "848f4b8b-7c9b-4298-b2c8-21255f9d07fe",
"name": "Code1",
"type": "n8n-nodes-base.code",
"position": [
1856,
1216
],
"parameters": {
"jsCode": "// Build RFC3339 start/end datetimes for Google Calendar\n\n// Use agreed_datetime if present; else default to tomorrow 14:00 UTC\nlet start = $json.agreed_datetime\n ? new Date($json.agreed_datetime)\n : new Date(Date.now() + 24 * 60 * 60 * 1000);\n\nif (!$json.agreed_datetime) {\n // set default to 14:00 UTC\n start.setUTCHours(14, 0, 0, 0);\n}\n\n// 15-minute meeting by default\nconst end = new Date(start.getTime() + 15 * 60 * 1000);\n\n// Attach ISO strings for the Calendar node\nreturn [{\n json: {\n ...$json,\n start_iso: start.toISOString(), // e.g., 2025-09-26T13:00:00.000Z\n end_iso: end.toISOString(), // e.g., 2025-09-26T13:15:00.000Z\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "a7a4446b-716f-4fc4-a0cf-51a77d7b9ca9",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
160,
144
],
"parameters": {
"color": 4,
"width": 720,
"height": 1512,
"content": "### Qualify and Call Back Inbound Leads with OpenAI, Bland AI, Airtable & SendGrid\n\nThis n8n template demonstrates how to automatically capture, qualify, and follow up with inbound leads using AI β including an outbound voice agent that calls hot leads, books a meeting on Google Calendar, and confirms by email β reducing speed-to-lead from hours to seconds.\n\n### Use Cases\n\n- Founders and agencies losing inbound leads because no one follows up fast enough\n- Sales teams that want voice follow-up on hot leads without hiring an SDR\n- Businesses running paid ads to a form/quiz and needing instant qualification + booking\n\n### How It Works\n\n1. **Trigger**: Webhook receives the lead from your form, quiz, or landing page (expects `name`, `email`, `phone`, `company`, and any qualification fields).\n2. **Validate & Log**:\n - Code node normalises and cleans the payload\n - IF node rejects submissions missing required fields\n - Lead is written to Airtable\n3. **AI Qualification**: OpenAI scores the lead and returns one of three next actions:\n - `nurture_email`\n - `priority_email`\n - `ai_call`\n4. **Routing**:\n - If low-intent:\n - Sends a nurture email via SendGrid\n - If medium-intent:\n - Sends a priority email via SendGrid\n - If high-intent:\n - Pulls free slots from Google Calendar\n - Formats them into a natural-speech sentence\n - Triggers a Bland AI voice agent to call the lead with a qualification + booking script\n5. **Call Outcome Handling**: A second webhook in the same workflow receives Bland AI's post-call payload:\n - If a slot was booked:\n - Creates the Google Calendar event\n - Updates the Airtable record with call outcome and meeting time\n - Sends a booking confirmation email via SendGrid\n - If the call failed (no answer, declined, error):\n - Updates the Airtable record with the failure reason\n - Sends a priority follow-up email so a human can step in\n\n### Customisation Options\n\n- Swap Bland AI for Vapi, Retell, or any voice agent with HTTP + callback support\n- Replace SendGrid with Gmail, Postmark, or Resend\n- Replace Airtable with Notion, Google Sheets, or HubSpot\n- Add a Slack node to ping the team in parallel when a hot lead comes in\n- Tune the qualification prompt and voice script to match your ICP and offer\n- Add a priority threshold so only deals over a certain size trigger the voice call\n\n### Prerequisites/Credential Setup\n\nTo use this workflow securely, you'll need the following credentials set up in n8n:\n\n- **OpenAI API** β for the lead qualification node\n- **Airtable Personal Access Token** β to write and update the `Leads` table\n- **SendGrid API** β to send nurture, priority, and confirmation emails\n- **Google Calendar OAuth2** β to read availability and create booked events\n- **Bland AI API key** β via an HTTP Header Auth credential in n8n; used by the **Trigger AI Phone Call** node\n\n### Secure Configuration\n\n- All credential fields use **n8n Credential Types**\n- No API keys, base IDs, or webhook URLs are hardcoded\n- The Bland AI callback URL must be set to your production **Call Outcome Webhook** URL before going live\n\n### Why This Helps\n\n- Cuts speed-to-lead from hours to under a minute on hot leads\n- Removes the manual \"review the form submission\" step entirely\n- Books meetings without a human ever picking up the phone\n- Maintains a clean Airtable audit trail of every lead, qualification decision, and call outcome\n\n---\n\nWith this template, founders and small sales teams can qualify and follow up with every inbound lead automatically, including voice outreach on the highest-intent ones β without adding headcount.\n"
},
"typeVersion": 1
},
{
"id": "8b434062-0fcc-4040-81df-f8c899086468",
"name": "Section β Intake & validation",
"type": "n8n-nodes-base.stickyNote",
"position": [
912,
320
],
"parameters": {
"color": 5,
"width": 1188,
"height": 568,
"content": "### 1. Intake & validation\n- **Webhook** receives the form payload (expects `name`, `email`, `phone`, `company`, etc.)\n- **Validate & Clean Data** normalises fields and flags missing required values\n- **Check Validation Status** routes invalid leads back to the form\n- **Create a record** writes the validated lead to Airtable\n- **Respond to Webhook** returns a 200 to the form so the user sees the thank-you state"
},
"typeVersion": 1
},
{
"id": "5ddd4200-adfd-415e-8e00-293492dbeed6",
"name": "Section β AI qualification & routing",
"type": "n8n-nodes-base.stickyNote",
"position": [
2128,
288
],
"parameters": {
"color": 6,
"width": 736,
"height": 552,
"content": "### 2. AI qualification\n- **AI Lead Qualification** (OpenAI) scores the lead and returns a recommended next action: `nurture_email`, `priority_email`, or `ai_call`.\n- **Check for Follow-Up Sequence** branches cold leads to the nurture path.\n- **Check for AI Calling** branches hot leads to the Bland AI voice path.\n\nTune the prompt inside the OpenAI node to match your qualification criteria."
},
"typeVersion": 1
},
{
"id": "e5b59b02-d371-43f1-a342-f65eeed5065d",
"name": "Section β AI voice call",
"type": "n8n-nodes-base.stickyNote",
"position": [
2896,
288
],
"parameters": {
"color": 3,
"width": 848,
"height": 552,
"content": "### 3. AI voice call (Bland AI)\n- **Get availability in a calendar** pulls free slots from Google Calendar.\n- **Code in JavaScript** formats those slots into a human sentence for the voice agent.\n- **Trigger AI Phone Call** posts to Bland AI with the script, the lead's number, and the n8n callback URL (the second webhook in this workflow).\n\nReplace the `webhook` URL in the Bland AI body with your live Call Outcome webhook URL before going live."
},
"typeVersion": 1
},
{
"id": "f9365c7e-4772-46d3-ba2a-637caf2548b6",
"name": "Section β Call outcome & booking",
"type": "n8n-nodes-base.stickyNote",
"position": [
912,
1056
],
"parameters": {
"color": 7,
"width": 1864,
"height": 572,
"content": "### 4. Call outcome β booking\n- **Call Outcome Webhook** receives Bland AI's post-call payload.\n- **Parse Call Outcome** extracts the booked slot, lead email, and call status.\n- **Check Call Success** branches the booked path vs. the failed path.\n\n**Success path:** create the Google Calendar event β log the call in Airtable β send the booking confirmation email via SendGrid.\n\n**Failure path:** update the Airtable record and send a priority follow-up email so a human can step in."
},
"typeVersion": 1
},
{
"parameters": {
"operation": "verify",
"email": "={{ $node[\"Validate & Clean Data\"].json.lead.email }}",
"additionalOptions": {}
},
"type": "n8n-nodes-billionverify.billionVerify",
"typeVersion": 1,
"position": [
3112,
640
],
"name": "Verify Email (BillionVerify)",
"credentials": {
"billionVerifyApi": {
"id": "",
"name": "BillionVerify account"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "is-deliverable",
"leftValue": "={{ $json.is_deliverable }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
]
}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
3292,
640
],
"name": "IF deliverable"
},
{
"parameters": {
"operation": "verify",
"email": "={{ $('Validate & Clean Data').item.json.lead.email }}",
"additionalOptions": {}
},
"type": "n8n-nodes-billionverify.billionVerify",
"typeVersion": 1,
"position": [
2072,
640
],
"name": "Verify Email (BillionVerify) 2",
"credentials": {
"billionVerifyApi": {
"id": "",
"name": "BillionVerify account"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "is-deliverable",
"leftValue": "={{ $json.is_deliverable }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
]
}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
2252,
640
],
"name": "IF deliverable 2"
},
{
"parameters": {
"operation": "verify",
"email": "={{ \n String(\n $json.fields?.Email \n || $('Parse Call Outcome').item.json.lead_email \n || $items('Call Outcome Webhook',0,0).json.body?.variables?.metadata?.lead_email \n || ''\n ).trim()\n}}\n",
"additionalOptions": {}
},
"type": "n8n-nodes-billionverify.billionVerify",
"typeVersion": 1,
"position": [
2168,
1216
],
"name": "Verify Email (BillionVerify) 3",
"credentials": {
"billionVerifyApi": {
"id": "",
"name": "BillionVerify account"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "is-deliverable",
"leftValue": "={{ $json.is_deliverable }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
]
}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
2348,
1216
],
"name": "IF deliverable 3"
},
{
"parameters": {
"operation": "verify",
"email": "={{ \n String(\n $json.fields?.Email \n || $('Parse Call Outcome').item.json.lead_email \n || $items('Call Outcome Webhook',0,0).json.body?.variables?.metadata?.lead_email \n || ''\n ).trim()\n}}\n",
"additionalOptions": {}
},
"type": "n8n-nodes-billionverify.billionVerify",
"typeVersion": 1,
"position": [
1496,
1408
],
"name": "Verify Email (BillionVerify) 4",
"credentials": {
"billionVerifyApi": {
"id": "",
"name": "BillionVerify account"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "is-deliverable",
"leftValue": "={{ $json.is_deliverable }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
]
}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1676,
1408
],
"name": "IF deliverable 4"
}
],
"connections": {
"Code": {
"main": [
[
{
"node": "Check Call Success",
"type": "main",
"index": 0
}
]
]
},
"Code1": {
"main": [
[
{
"node": "Create Calendar event",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Validate & Clean Data",
"type": "main",
"index": 0
}
]
]
},
"Create a record": {
"main": [
[
{
"node": "Check for AI Calling",
"type": "main",
"index": 0
}
]
]
},
"Check Call Success": {
"main": [
[
{
"node": "Code1",
"type": "main",
"index": 0
}
],
[
{
"node": "Verify Email (BillionVerify) 4",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript": {
"main": [
[
{
"node": "Trigger AI Phone Call",
"type": "main",
"index": 0
}
]
]
},
"Parse Call Outcome": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Update Call Record": {
"main": [
[
{
"node": "Check for Follow-Up Sequence",
"type": "main",
"index": 0
}
]
]
},
"Send Priority Email": {
"main": [
[
{
"node": "Update Call Record",
"type": "main",
"index": 0
}
]
]
},
"Call Outcome Webhook": {
"main": [
[
{
"node": "Parse Call Outcome",
"type": "main",
"index": 0
},
{
"node": "Respond to Webhook1",
"type": "main",
"index": 0
}
]
]
},
"Check for AI Calling": {
"main": [
[
{
"node": "Get availability in a calendar",
"type": "main",
"index": 0
}
],
[
{
"node": "Verify Email (BillionVerify) 2",
"type": "main",
"index": 0
}
]
]
},
"AI Lead Qualification": {
"main": [
[
{
"node": "Check Validation Status",
"type": "main",
"index": 0
}
]
]
},
"Create Calendar event": {
"main": [
[
{
"node": "Add Successful Call Record",
"type": "main",
"index": 0
}
]
]
},
"Validate & Clean Data": {
"main": [
[
{
"node": "AI Lead Qualification",
"type": "main",
"index": 0
}
]
]
},
"Check Validation Status": {
"main": [
[
{
"node": "Create a record",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"Add Successful Call Record": {
"main": [
[
{
"node": "Verify Email (BillionVerify) 3",
"type": "main",
"index": 0
}
]
]
},
"Check for Follow-Up Sequence": {
"main": [
[
{
"node": "Verify Email (BillionVerify)",
"type": "main",
"index": 0
}
]
]
},
"Get availability in a calendar": {
"main": [
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
]
]
},
"Send Priority Email for Failed Calls": {
"main": [
[
{
"node": "Update Failed Call Record",
"type": "main",
"index": 0
}
]
]
},
"Verify Email (BillionVerify)": {
"main": [
[
{
"node": "IF deliverable",
"type": "main",
"index": 0
}
]
]
},
"IF deliverable": {
"main": [
[
{
"node": "Send Nurture Email",
"type": "main",
"index": 0
}
],
[]
]
},
"Verify Email (BillionVerify) 2": {
"main": [
[
{
"node": "IF deliverable 2",
"type": "main",
"index": 0
}
]
]
},
"IF deliverable 2": {
"main": [
[
{
"node": "Send Priority Email",
"type": "main",
"index": 0
}
],
[]
]
},
"Verify Email (BillionVerify) 3": {
"main": [
[
{
"node": "IF deliverable 3",
"type": "main",
"index": 0
}
]
]
},
"IF deliverable 3": {
"main": [
[
{
"node": "Send Booking ConfirmationEmail",
"type": "main",
"index": 0
}
],
[]
]
},
"Verify Email (BillionVerify) 4": {
"main": [
[
{
"node": "IF deliverable 4",
"type": "main",
"index": 0
}
]
]
},
"IF deliverable 4": {
"main": [
[
{
"node": "Send Priority Email for Failed Calls",
"type": "main",
"index": 0
}
],
[]
]
}
},
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
}
}Workflow templates with Bland AI
Ready-to-use workflows that verify emails before Bland AI sends.
How it works
- 1
Add BillionVerify to your n8n or Integrately workflow that feeds contacts into Bland AI campaigns.
- 2
When a new contact enters the pipeline β from a form, CRM, or spreadsheet β their email is sent to BillionVerify for real-time validation.
- 3
BillionVerify checks deliverability, disposable domain status, catch-all configuration, and role-address patterns.
- 4
Contacts with verified emails are queued for Bland AI calls with confidence that follow-up emails will land; flagged contacts are held for review.
- 5
Verification results are stored against each contact record so your team can filter or prioritize accordingly.
When to use this
Validate contact emails before AI call campaigns
Before Bland AI dials a contact, verify their email address with BillionVerify so that any post-call email follow-up β summary, booking link, or document β reaches a real inbox rather than bouncing.
Inbound lead qualification
When inbound leads provide their email to trigger a Bland AI callback, run the address through BillionVerify first. Disposable or invalid addresses often signal low-intent leads, helping you prioritize the call queue.
FAQ
Why does email verification matter for a voice AI platform?
Most AI calling workflows send email follow-ups β confirmations, summaries, or next steps. If those emails bounce, the call's value is lost. Verifying upfront ensures the full outreach sequence works end to end.
Can BillionVerify help identify low-quality leads before a call is placed?
Yes. Contacts who provide disposable or clearly fake email addresses often have low intent. Flagging them before the call queue helps your AI calling campaigns focus on higher-quality prospects.
How do I connect BillionVerify with Bland AI?
Use the BillionVerify n8n community node to add a verification step before your Bland AI call action, or connect both services through Integrately with a 1-click integration. The REST API works for custom pipelines too.
Verify emails in Bland AI
Create a free account, grab your API key, and stop bounces before they happen.
Get started free