This workflow corresponds to n8n.io template #11023 — we link there as the canonical source.
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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "61cb1154-9052-4c6f-ae7d-d1a817988b5c",
"name": "Decodo: Google Search",
"type": "@decodo/n8n-nodes-decodo.decodo",
"position": [
-96,
-208
],
"parameters": {
"geo": "={{ $json.target_geo }}",
"query": "={{ $('Config: Set Search Params').item.json.tech_footprint + \" \" + $('Config: Set Search Params').item.json.target_industry }}",
"locale": "={{ $json.target_locale }}",
"headless": false,
"operation": "google_search",
"results_limit": 50
},
"credentials": {
"decodoApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "8c326ac9-8c83-4471-97db-23f1d0bf3eac",
"name": "Decodo: Scrape Contact Page",
"type": "@decodo/n8n-nodes-decodo.decodo",
"position": [
3152,
0
],
"parameters": {
"url": "={{ $json.contact_page_url }}",
"headless": false
},
"credentials": {
"decodoApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "e79c81d0-9794-46a1-abfc-e4820d7343fa",
"name": "If: Lead Exists?",
"type": "n8n-nodes-base.if",
"position": [
992,
-192
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "3ecc863b-c175-4ddc-9c07-b69aafa3572c",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.isEmpty() }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "993802a8-1c83-4696-8917-935e0abae41c",
"name": "Airtable: Create Lead",
"type": "n8n-nodes-base.airtable",
"position": [
1360,
16
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "app6qdzRRG0zi7rY2",
"cachedResultUrl": "https://airtable.com/app6qdzRRG0zi7rY2",
"cachedResultName": "Leads"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblOsYSQEXcKUdUtD",
"cachedResultUrl": "https://airtable.com/app6qdzRRG0zi7rY2/tblOsYSQEXcKUdUtD",
"cachedResultName": "Leads"
},
"columns": {
"value": {
"Domain": "={{ $('Loop: Process Leads').item.json.domain }}",
"Status": "New Lead",
"Lead Type": "={{ $('Loop: Process Leads').item.json.lead_type }}",
"Date Added": "={{ $now }}",
"Source URL": "={{ $('Loop: Process Leads').item.json.source_url }}",
"Date Updated": "={{ $now }}"
},
"schema": [
{
"id": "Domain",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Domain",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Primary Email",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Primary Email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Contact Page URL",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Contact Page URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Source URL",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Source URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Lead Type",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Lead Type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "options",
"display": true,
"options": [
{
"name": "New Lead",
"value": "New Lead"
},
{
"name": "Updated",
"value": "Updated"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Date Added",
"type": "dateTime",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Date Added",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Date Updated",
"type": "dateTime",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Date Updated",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "create"
},
"credentials": {
"airtableTokenApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "c1f1f2b0-3ee7-4a27-a0f8-8adaae810da3",
"name": "Airtable: Update Lead",
"type": "n8n-nodes-base.airtable",
"position": [
1360,
-416
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "app6qdzRRG0zi7rY2",
"cachedResultUrl": "https://airtable.com/app6qdzRRG0zi7rY2",
"cachedResultName": "Leads"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblOsYSQEXcKUdUtD",
"cachedResultUrl": "https://airtable.com/app6qdzRRG0zi7rY2/tblOsYSQEXcKUdUtD",
"cachedResultName": "Leads"
},
"columns": {
"value": {
"id": "={{ $json.id }}",
"Status": "Updated",
"Lead Type": "={{ $('Loop: Process Leads').item.json.lead_type }}",
"Source URL": "={{ $('Loop: Process Leads').item.json.source_url }}",
"Date Updated": "={{ $now }}"
},
"schema": [
{
"id": "id",
"type": "string",
"display": true,
"removed": false,
"readOnly": true,
"required": false,
"displayName": "id",
"defaultMatch": true
},
{
"id": "Domain",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Domain",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Primary Email",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Primary Email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Contact Page URL",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Contact Page URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Source URL",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Source URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Lead Type",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Lead Type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "options",
"display": true,
"options": [
{
"name": "New Lead",
"value": "New Lead"
},
{
"name": "Updated",
"value": "Updated"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Date Added",
"type": "dateTime",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Date Added",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Date Updated",
"type": "dateTime",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Date Updated",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update"
},
"credentials": {
"airtableTokenApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "a2d23095-83f2-42df-bc8c-033a0e3bc14d",
"name": "Loop: Process Leads",
"type": "n8n-nodes-base.splitInBatches",
"position": [
512,
-208
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "24e41037-2074-4e51-9b34-9ce021c48ed4",
"name": "Decodo: Email Search",
"type": "@decodo/n8n-nodes-decodo.decodo",
"position": [
2208,
16
],
"parameters": {
"geo": "={{ $('Config: Set Search Params').item.json.target_geo }}",
"query": "={{ 'site:' + $json.domain + ' (inurl:contact OR inurl:about OR intitle:\"Contact Us\" OR intitle:\"About Us\")' }}",
"locale": "={{ $('Config: Set Search Params').item.json.target_locale }}",
"headless": false,
"operation": "google_search",
"results_limit": 20
},
"credentials": {
"decodoApi": {
"name": "<your credential>"
}
},
"executeOnce": false,
"typeVersion": 1
},
{
"id": "30652bbf-15ac-4324-9090-b8f3964f1177",
"name": "Code: Initial Enrichment",
"type": "n8n-nodes-base.code",
"position": [
2512,
16
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// NOTE: We assume the Decodo search results are merged into the root \n// under the key 'results' (the large search result array).\n\nconst mergedItem = $input.item.json; \n\n// Path to the Decodo results: mergedItem.results[0].content.results\n// We access the nested organic/paid arrays from this location:\nconst decodoContent = mergedItem.results?.[0]?.content?.results?.results || {}; \n\nconst organicResults = Array.isArray(decodoContent.organic) ? decodoContent.organic : [];\nconst paidResults = Array.isArray(decodoContent.paid) ? decodoContent.paid : [];\n\n\nconst emailRegex = /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b/g;\nconst contactUrlRegex = /(contact|about|support)\\-?us|imprint|privacy/i;\n\nlet foundEmail = '';\nlet contactPage = '';\n\n// Function to check both paid and organic snippets for data\nfunction extractDataFromResults(results) {\n if (!Array.isArray(results)) return;\n\n for (const result of results) {\n // The snippet text is in the 'desc' field\n const snippetText = result.desc || '';\n \n // 1. Extract Email\n const emailMatch = snippetText.match(emailRegex);\n if (emailMatch && !foundEmail) {\n foundEmail = emailMatch[0]; // Take the first one found\n }\n\n // 2. Extract Contact Page URL\n if (result.url && result.url.match(contactUrlRegex) && !contactPage) {\n contactPage = result.url; // Take the first relevant URL found\n }\n }\n}\n\n// Process results from both arrays\nextractDataFromResults(organicResults);\nextractDataFromResults(paidResults);\n\n// Return a single object, merging the new contact info with the existing combined metadata\nreturn {\n json: {\n ...mergedItem, // \u2b05\ufe0f Preserves ALL metadata (lead_id, status, etc., and previous search results)\n contact_email: foundEmail, // \u2b05\ufe0f Add new email (or keep it if empty)\n // Prefer new contact page, fall back to existing metadata links\n contact_page_url: contactPage\n }\n};"
},
"typeVersion": 2
},
{
"id": "99a61acf-57e8-408d-b9ec-24a52b308731",
"name": "If: Enrichment Needed?",
"type": "n8n-nodes-base.if",
"position": [
1600,
-416
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "5b1aa932-866d-4659-a781-2539f2bd17ea",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.fields[\"Primary Email\"] }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "9129d716-c5e3-4182-bb96-ef95ced84ed6",
"name": "If: Contact Page Exists?",
"type": "n8n-nodes-base.if",
"position": [
2752,
16
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d9f4d10d-7b27-406a-a6ba-f810e31a0c3b",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.contact_page_url }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "800d85e2-a896-40f7-8d96-674a9a24c47e",
"name": "If: Contact Email Exists?",
"type": "n8n-nodes-base.if",
"position": [
3760,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "d05bde26-9607-4c09-8182-c9ade9db8514",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.final_contact_email }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "e2ec9ccd-47d3-4a90-8aeb-df8d6621a039",
"name": "Airtable: Update Lead Contact Email",
"type": "n8n-nodes-base.airtable",
"position": [
4032,
-16
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "app6qdzRRG0zi7rY2",
"cachedResultUrl": "https://airtable.com/app6qdzRRG0zi7rY2",
"cachedResultName": "Leads"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblOsYSQEXcKUdUtD",
"cachedResultUrl": "https://airtable.com/app6qdzRRG0zi7rY2/tblOsYSQEXcKUdUtD",
"cachedResultName": "Leads"
},
"columns": {
"value": {
"id": "={{ $('Data Merger: ID Finalizer').item.json.airtable_id }}",
"Status": "Enrichment Complete",
"Date Updated": "={{ $now }}",
"Primary Email": "={{ $('Code: Finalize Contact Data').item.json.final_contact_email }}",
"Contact Page URL": "={{ $('Code: Initial Enrichment').item.json.contact_page_url }}"
},
"schema": [
{
"id": "id",
"type": "string",
"display": true,
"removed": false,
"readOnly": true,
"required": false,
"displayName": "id",
"defaultMatch": true
},
{
"id": "Domain",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Domain",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Primary Email",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Primary Email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Contact Page URL",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Contact Page URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Source URL",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Source URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Lead Type",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Lead Type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "options",
"display": true,
"options": [
{
"name": "New Lead",
"value": "New Lead"
},
{
"name": "Updated",
"value": "Updated"
},
{
"name": "Enrichment Complete",
"value": "Enrichment Complete"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Date Added",
"type": "dateTime",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Date Added",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Date Updated",
"type": "dateTime",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Date Updated",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update"
},
"credentials": {
"airtableTokenApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "b73304d7-b9b7-493b-806a-527c08684145",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-944,
-208
],
"parameters": {},
"typeVersion": 1
},
{
"id": "c42d696f-bdde-4224-a3e5-ecef78c8578c",
"name": "Config: Set Search Params",
"type": "n8n-nodes-base.set",
"position": [
-576,
-208
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "8b8b2663-bfe9-432e-812b-e43a93ff6f15",
"name": "tech_footprint",
"type": "string",
"value": "We use Klaviyo"
},
{
"id": "eb124d7e-4157-4c7a-a939-be8241962615",
"name": "target_industry",
"type": "string",
"value": ""
},
{
"id": "b9886785-eddf-46c0-a840-78130b349ed2",
"name": "target_geo",
"type": "string",
"value": "United States"
},
{
"id": "4a19c78e-e02d-4c48-8bc9-514bdce02c86",
"name": "target_locale",
"type": "string",
"value": "en-US"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "bcb527c8-d0df-42bc-a2a9-67c82513309c",
"name": "Code: Initial Domain Filter",
"type": "n8n-nodes-base.code",
"position": [
128,
-208
],
"parameters": {
"jsCode": "// Function to convert Base64 string to a Uint8Array (pure JS replacement for Buffer.from(..., 'base64'))\nfunction base64ToUint8Array(base64) {\n const binaryString = atob(base64);\n const len = binaryString.length;\n const bytes = new Uint8Array(len);\n for (let i = 0; i < len; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n return bytes;\n}\n\n// Function to extract domain without using the URL constructor\nfunction getDomain(fullUrl) {\n try {\n // Simple string manipulation to find and clean the domain\n const match = fullUrl.match(/^(?:https?:\\/\\/)?(?:[^@\\n]+@)?(?:www\\.)?([^:\\/\\n\\?]+)/i);\n if (match && match[1]) {\n return match[1];\n }\n return '';\n } catch (e) {\n return '';\n }\n}\n\n\nconst outputLeads = [{ source_url: '', source_title: '', domain: '', lead_type: '' }].filter(item => false);\nconst uniqueDomains = new Set();\nconst dataContainer = $input.first().json.results?.[0]?.content?.results?.results;\n\nif (!dataContainer) {\n // Gracefully handle missing data structure\n return [{ json: { error: \"Decodo: Expected data container not found.\" } }];\n}\n\n\n// Helper function to process and clean individual search results\nconst processResult = (item, type) => {\n const fullUrl = item.url || (item.snippet && item.snippet.link);\n const title = item.title || item.desc || (item.snippet && item.snippet.title);\n \n if (!fullUrl || !title) return;\n\n const domain = getDomain(fullUrl);\n\n if (domain && !uniqueDomains.has(domain)) {\n uniqueDomains.add(domain);\n outputLeads.push({\n source_url: fullUrl,\n source_title: title,\n domain: domain,\n lead_type: type\n });\n }\n};\n\n\n// Robustly check for and process array structures\nconst paidResults = Array.isArray(dataContainer.paid) ? dataContainer.paid : [];\nfor (const item of paidResults) {\n processResult(item, 'Paid Ad Lead');\n}\n\nconst organicResults = Array.isArray(dataContainer.organic) ? dataContainer.organic : [];\nfor (const item of organicResults) {\n processResult(item, 'Organic Lead');\n}\n\nreturn outputLeads;"
},
"typeVersion": 2
},
{
"id": "268cd586-242b-4a26-bc27-aa8dfe5dad62",
"name": "Airtable: Check Existence",
"type": "n8n-nodes-base.airtable",
"position": [
768,
-192
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "app6qdzRRG0zi7rY2",
"cachedResultUrl": "https://airtable.com/app6qdzRRG0zi7rY2",
"cachedResultName": "Leads"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblOsYSQEXcKUdUtD",
"cachedResultUrl": "https://airtable.com/app6qdzRRG0zi7rY2/tblOsYSQEXcKUdUtD",
"cachedResultName": "Leads"
},
"options": {},
"operation": "search",
"filterByFormula": "={{ \"({Domain} = '\" + $json.domain + \"')\" }}"
},
"credentials": {
"airtableTokenApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.1,
"alwaysOutputData": true
},
{
"id": "ae15497e-a4d7-4fc3-bb1a-14fa1d1b9d83",
"name": "Code: Finalize Contact Data",
"type": "n8n-nodes-base.code",
"position": [
3376,
0
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Function to extract high-value emails using regex\nfunction extractEmail(text) {\n // Regex for standard email format\n const emailRegex = /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b/g;\n const allEmails = text.match(emailRegex) || [];\n \n // Convert to a Set to ensure unique emails\n const uniqueEmails = [...new Set(allEmails)];\n \n let bestEmail = '';\n \n // 1. Prioritize High-Value Emails (Names, Sales, Founder)\n const priorityKeywords = /^(sales|contact|founder|ceo|director|manager|president|[a-z]{1})\\W*\\w*@/i;\n \n for (const email of uniqueEmails) {\n // Check if the part before the @ matches a priority keyword\n if (priorityKeywords.test(email.split('@')[0])) {\n bestEmail = email;\n break; \n }\n }\n \n // 2. Fallback to Generic Emails (info, contact, support)\n if (!bestEmail) {\n const fallbackKeywords = /^(info|contact|support)/i;\n for (const email of uniqueEmails) {\n if (fallbackKeywords.test(email.split('@')[0])) {\n bestEmail = email;\n break;\n }\n }\n }\n \n // 3. Fallback to the first available unique email\n if (!bestEmail && uniqueEmails.length > 0) {\n bestEmail = uniqueEmails[0];\n }\n \n return bestEmail;\n}\n\n// Get the raw HTML/text output from the Decodo Scrape Contact Page node\n// We assume the Decodo Scrape returns the content in the 'content' field for the single item\nconst rawContent = $input.item.json.results[0].content || '';\n\n// The existing lead data (domain, lead_type, etc.) is passed through via $json\nconst finalEmail = extractEmail(rawContent);\n\nreturn {\n json: {\n ...$json, // Keep previous data\n final_contact_email: finalEmail, // The highest-quality email found\n all_emails_found: finalEmail ? 1 : 0 // Simple count for verification\n }\n};"
},
"typeVersion": 2
},
{
"id": "a5df343a-678e-46b7-abaa-910d12d0d564",
"name": "Data Merger: ID Finalizer",
"type": "n8n-nodes-base.set",
"position": [
1984,
16
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "fa6f60dc-c374-4095-8016-f6f9e4d47b20",
"name": "airtable_id",
"type": "string",
"value": "={{ $json.id }}"
},
{
"id": "093b8c76-4e26-4e0d-a1b6-e4a0121bdfc7",
"name": "domain",
"type": "string",
"value": "={{ $json.fields.Domain }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "6bbfe143-f892-47d9-8915-db0cc92bf8f2",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1632,
-848
],
"parameters": {
"width": 592,
"height": 848,
"content": "# Digital Footprint Lead Generation: Decodo & Airtable\n\n## How It Works\nThe workflow executes a dedicated **Find/Create/Update** sequence for every lead:\n\n1. **Search & Standardize:** Decodo executes a Google Search (Dork). The results are cleaned and filtered into a unique domain array.\n2. **Loop & Check:** The **Loop Over Items** starts. An **Airtable Read** node checks the database for the lead's existence.\n3. **Conditional Enrichment:** The **If: Enrichment Needed?** node checks the existing `Primary Email` status. If it's empty, the lead proceeds to the deep scraping pipeline (Decodo: Scrape Contact Page). This ensures credits are only spent on incomplete leads.\n4. **Final Update:** The final node updates the Airtable record with the high-quality email address found from the deep scrape.\n\n## How to Setup\nThis template requires specific node configuration and Airtable fields.\n\n1. **Credentials:** Obtain API keys for **Decodo** and **Airtable**.\n2. **Airtable Setup (Schema):** Create an Airtable base with a 'Leads' table. It must include these fields for mapping:\n * `Domain` (Single Line Text - Primary Field)\n * `Primary Email` (Email)\n * `Contact Page URL` (URL)\n * `Source URL` (URL)\n * `Lead Type` (Single Select: *Paid Ad Lead, Organic Lead*)\n * `Status` (Single Select: *New Lead, Updated, Enrichment Complete*)\n3. **Global Configuration:** Open the **`Config: Set Search Params`** node. Customize the following fields:\n * `tech_footprint`: e.g., `\"We use Klaviyo\"`\n * `target_industry`: e.g., `site:promarketer.ca`\n"
},
"typeVersion": 1
},
{
"id": "ad30e671-980f-4faf-9ce1-1e15d09f858f",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-768,
-416
],
"parameters": {
"color": 7,
"width": 480,
"height": 416,
"content": "## Configuration: Set Search Parameters\nThis node is the central control panel for lead generation. Change values to define your target market, technology, and search scope."
},
"typeVersion": 1
},
{
"id": "06083a07-924b-4a07-9955-852c27e3ecac",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
3008,
-208
],
"parameters": {
"color": 7,
"width": 608,
"height": 448,
"content": "## Deep Enrichment Pipeline\n* **Decodo** conditionally scrapes the target contact page only if data is missing.\n* **The Code:** Final Email Data node then uses Regex to extract the highest-quality email (like sales@) from the page content."
},
"typeVersion": 1
},
{
"id": "b4616783-f8ef-4ccf-a3c8-85d1d0e5983d",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1856,
-208
],
"parameters": {
"color": 7,
"width": 576,
"height": 448,
"content": "## Data Consolidation & Continuation\n**ID Finalizer** extracts the **Airtable Record ID** and merges it with original lead data. This single item then proceeds to the **Decodo Email Search** node for deep enrichment."
},
"typeVersion": 1
},
{
"id": "756977e3-abb9-4fc5-8afa-65e73f4be417",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
416,
-416
],
"parameters": {
"color": 7,
"width": 784,
"height": 416,
"content": "## Database Integrity Loop\nThe loop processes each lead and runs the Find/Create/Update logic.\n**Airtable: Check Existence** prevents duplicates. If the lead is **New**, it's **Created**; if **Existing**, it's **Updated**. This establishes the permanent **Airtable Record ID** for all future enrichment steps.\n"
},
"typeVersion": 1
},
{
"id": "33f1e17c-36fd-4919-82ac-02129c4deb97",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-208,
-416
],
"parameters": {
"color": 7,
"width": 544,
"height": 416,
"content": "## Data Sourcing & Lead List Generation\n* **Decodo: Google Search** executes the customized query (JS Rendering disabled for cost).\n* **Code: Initial Domain Filter** then processes the raw search results, extracts clean domains, and removes duplicates to generate the final, standardized lead array."
},
"typeVersion": 1
},
{
"id": "81cc8577-b141-49f1-89e2-049a3baf8504",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
-208,
-656
],
"parameters": {
"color": 3,
"width": 544,
"height": 208,
"content": "## \ud83c\udf81 Exclusive 80% Discount!\n\nGet **80% OFF** the **23k Advanced Scraping API** plan at Decodo using this workflow.\n\n**Coupon Code:** `ATTAN8N`\n\n\ud83d\udc49 [**Click here to Sign Up & Claim**](https://visit.decodo.com/c/6679292/3071239/17480)\n"
},
"typeVersion": 1
}
],
"connections": {
"Manual Trigger": {
"main": [
[
{
"node": "Config: Set Search Params",
"type": "main",
"index": 0
}
]
]
},
"If: Lead Exists?": {
"main": [
[
{
"node": "Airtable: Update Lead",
"type": "main",
"index": 0
}
],
[
{
"node": "Airtable: Create Lead",
"type": "main",
"index": 0
}
]
]
},
"Loop: Process Leads": {
"main": [
[],
[
{
"node": "Airtable: Check Existence",
"type": "main",
"index": 0
}
]
]
},
"Decodo: Email Search": {
"main": [
[
{
"node": "Code: Initial Enrichment",
"type": "main",
"index": 0
}
]
]
},
"Airtable: Create Lead": {
"main": [
[
{
"node": "Data Merger: ID Finalizer",
"type": "main",
"index": 0
}
]
]
},
"Airtable: Update Lead": {
"main": [
[
{
"node": "If: Enrichment Needed?",
"type": "main",
"index": 0
}
]
]
},
"Decodo: Google Search": {
"main": [
[
{
"node": "Code: Initial Domain Filter",
"type": "main",
"index": 0
}
]
]
},
"If: Enrichment Needed?": {
"main": [
[
{
"node": "Data Merger: ID Finalizer",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop: Process Leads",
"type": "main",
"index": 0
}
]
]
},
"Code: Initial Enrichment": {
"main": [
[
{
"node": "If: Contact Page Exists?",
"type": "main",
"index": 0
}
]
]
},
"If: Contact Page Exists?": {
"main": [
[
{
"node": "Decodo: Scrape Contact Page",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop: Process Leads",
"type": "main",
"index": 0
}
]
]
},
"Airtable: Check Existence": {
"main": [
[
{
"node": "If: Lead Exists?",
"type": "main",
"index": 0
}
]
]
},
"Config: Set Search Params": {
"main": [
[
{
"node": "Decodo: Google Search",
"type": "main",
"index": 0
}
]
]
},
"Data Merger: ID Finalizer": {
"main": [
[
{
"node": "Decodo: Email Search",
"type": "main",
"index": 0
}
]
]
},
"If: Contact Email Exists?": {
"main": [
[
{
"node": "Airtable: Update Lead Contact Email",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop: Process Leads",
"type": "main",
"index": 0
}
]
]
},
"Code: Finalize Contact Data": {
"main": [
[
{
"node": "If: Contact Email Exists?",
"type": "main",
"index": 0
}
]
]
},
"Code: Initial Domain Filter": {
"main": [
[
{
"node": "Loop: Process Leads",
"type": "main",
"index": 0
}
]
]
},
"Decodo: Scrape Contact Page": {
"main": [
[
{
"node": "Code: Finalize Contact Data",
"type": "main",
"index": 0
}
]
]
},
"Airtable: Update Lead Contact Email": {
"main": [
[
{
"node": "Loop: Process Leads",
"type": "main",
"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.
airtableTokenApidecodoApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Stop manually searching Google for sales leads. Start listening to the internet.
Source: https://n8n.io/workflows/11023/ — 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.
Maximize your conversion rates with this end-to-end automated outreach and lead nurturing system. This workflow manages the entire sales lifecycle—from instant contact enrollment via WhatsApp to AI-pe
Automate your lead generation by scraping targeted prospects from Apollo.io, enriching with contact details, and seamlessly syncing to Airtable for organized outreach—all without manual data entry.
Get analytics of a website and store it Airtable. Uses manualTrigger, googleAnalytics, airtable. Event-driven trigger; 4 nodes.
This workflow is designed to take user inputs in order to generate an image using the Riverflow 2.0 model through the Replicate API. It can handle both image generation as well as image editing. Addit
Run professional email campaigns with A/B testing, Google Sheets tracking, and Slack analytics. FEATURES: