This workflow corresponds to n8n.io template #10760 — we link there as the canonical source.
This workflow follows the Chainllm → Google Sheets recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "IyJJlqKhDAIacooh",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Decodo Google Maps Lead Enrichment",
"tags": [],
"nodes": [
{
"id": "c54c9e66-ed51-41b3-9be0-c83fafd13965",
"name": "Parse & Normalize Data",
"type": "n8n-nodes-base.code",
"notes": "Normalizes Decodo response into clean lead objects. Handles different response formats.",
"position": [
-2160,
368
],
"parameters": {
"jsCode": "// Helper function to extract text with Regex\nfunction extractWithRegex(text, regex) {\n try {\n const match = text.match(regex);\n // [0] is full match, [1] is capture group first\n return match && match[1] ? match[1].trim() : '';\n } catch (e) {\n return '';\n }\n}\n\n// Helper function to extract text between two strings\nfunction extractBetween(text, startStr, endStr) {\n try {\n const startIndex = text.indexOf(startStr);\n if (startIndex === -1) return '';\n \n const textAfterStart = text.substring(startIndex + startStr.length);\n const endIndex = textAfterStart.indexOf(endStr);\n if (endIndex === -1) return '';\n \n return textAfterStart.substring(0, endIndex).trim();\n } catch (e) {\n return '';\n }\n}\n\n\nconst allLeads = [];\n\n// 1. Loop through the incoming n8n items\nfor (const item of items) {\n try {\n // item.json is the OBJECT: { \"results\": [...] }\n const dataObject = item.json; \n\n // 2. Check if the \"results\" array exists on this object\n if (dataObject && dataObject.results && Array.isArray(dataObject.results)) {\n \n // 3. Loop through the \"results\" array\n for (const [index, result] of dataObject.results.entries()) {\n \n const html = result.content;\n const searchQuery = result.query || 'unknown_query';\n\n if (!html || typeof html !== 'string') {\n continue;\n }\n\n // *** FIX: Split by the parent container 'jscontroller=\"AtSb\"' ***\n // This keeps the lat/lng div grouped with its business info\n const chunks = html.split('<div jscontroller=\"AtSb\"');\n chunks.shift(); // Remove the part before the first listing\n\n if (chunks.length === 0) {\n continue; \n }\n\n // Loop through each business HTML chunk\n for (const chunk of chunks) {\n \n const businessName = extractBetween(chunk, 'class=\"dbg0pd\" aria-level=\"3\" role=\"heading\"><span class=\"OSrXXb\">', '</span></div>');\n \n if (!businessName) {\n continue; // Not a valid listing\n }\n\n const detailsDiv = extractBetween(chunk, '</span></div><div>', '</div><div>');\n let address = detailsDiv;\n let phone = '';\n if (detailsDiv.includes('\u00b7')) {\n const parts = detailsDiv.split('\u00b7');\n address = parts[0] ? parts[0].trim() : '';\n phone = parts[1] ? parts[1].trim() : '';\n }\n \n const openingHours = extractBetween(chunk, detailsDiv + '</div><div>', '</div>');\n const description = extractBetween(chunk, '<span class=\"uDyWh OSrXXb btbrud\">\"', '\"</span>');\n\n // *** FIX: Made Regex more specific to get the \"Rute\" (Route) link ***\n let googleMapsUrl = extractWithRegex(chunk, /href=\"(\\/maps\\/dir\\/[^\"]*)\" style=\"cursor:pointer\"/);\n if (googleMapsUrl) {\n googleMapsUrl = 'https://www.google.com' + googleMapsUrl;\n }\n const website = extractWithRegex(chunk, /href=\"(http[^\"]*)\" data-ved=\"[^\"]*\">\\s*<div class=\"wLAgVc\"[^>]*>.*?<span class=\"BSaJxc\">Situs<\\/span>/);\n const orderUrl = extractWithRegex(chunk, /data-url=\"(https:\\/\\/www\\.google\\.com\\/viewer\\/chooseprovider[^\"]*)\"/);\n\n // *** FIX: Lat/Lng are now inside the 'chunk' because we split correctly ***\n const lat = extractWithRegex(chunk, /data-lat=\"(-?[0-9.]*)\"/);\n const lng = extractWithRegex(chunk, /data-lng=\"(-?[0-9.]*)\"/);\n const cid = extractWithRegex(chunk, /data-cid=\"([0-9]*)\"/);\n\n const lead = {\n id: cid || `lead_${searchQuery}_${index}`,\n businessName: businessName,\n category: 'Restoran',\n address: address,\n phone: phone,\n website: website,\n rating: 0,\n reviewCount: 0,\n latitude: lat ? parseFloat(lat) : null,\n longitude: lng ? parseFloat(lng) : null,\n openingHours: openingHours.replace(/<[^>]*>/g, ''),\n priceLevel: '',\n description: description,\n googleMapsUrl: googleMapsUrl,\n orderUrl: orderUrl,\n scrapedAt: new Date().toISOString(),\n searchQuery: searchQuery\n };\n \n allLeads.push({ json: lead });\n }\n }\n } else {\n allLeads.push({ json: { error: 'Skipped item: Input did not contain a .results array.', input: item.json } });\n }\n } catch (error) {\n allLeads.push({ json: { error: `Gagal parse item: ${error.message}`, input: item.json } });\n }\n}\n\n// 6. Return all extracted leads\nif (allLeads.length === 0) {\n throw new Error('No valid leads were extracted from the input data.');\n}\n\nreturn allLeads;"
},
"typeVersion": 2
},
{
"id": "ef0b0073-f13a-4125-ba73-ba00218a82c3",
"name": "Split Into Batches",
"type": "n8n-nodes-base.splitInBatches",
"notes": "Process leads in batches to avoid API rate limits and timeout issues.",
"position": [
-1840,
368
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "915d6026-d339-491f-a5c7-a0a680c9db30",
"name": "Save to Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"notes": "Saves enriched leads to Google Sheets. Use appendOrUpdate to avoid duplicates.",
"position": [
-1024,
256
],
"parameters": {
"columns": {
"value": {
"id": "={{ $json.id }}",
"phone": "={{ $json.phone }}",
"rating": "={{ $json.rating }}",
"address": "={{ $json.address }}",
"website": "={{ $json.website }}",
"category": "={{ $json.category }}",
"leadScore": "={{ $json.leadScore }}",
"scrapedAt": "={{ $json.scrapedAt }}",
"enrichedAt": "={{ $now.toDateTime().format('yyyy-MM-dd HH:mm:ss') }}",
"painPoints": "={{ $json.painPoints }}",
"reviewCount": "={{ $json.reviewCount }}",
"businessName": "={{ $json.businessName }}",
"outreachHook": "={{ $json.outreachHook }}",
"googleMaprUrl": "={{ $json.googleMapsUrl }}",
"valueProposition": "={{ $json.valueProposition }}",
"engagementStrategy": "={{ $json.engagementStrategy }}"
},
"schema": [
{
"id": "id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "id",
"defaultMatch": true,
"canBeUsedToMatch": true
},
{
"id": "leadsCategory",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "leadsCategory",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "businessName",
"type": "string",
"display": true,
"required": false,
"displayName": "businessName",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "category",
"type": "string",
"display": true,
"required": false,
"displayName": "category",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "address",
"type": "string",
"display": true,
"required": false,
"displayName": "address",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "phone",
"type": "string",
"display": true,
"required": false,
"displayName": "phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "website",
"type": "string",
"display": true,
"required": false,
"displayName": "website",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "rating",
"type": "string",
"display": true,
"required": false,
"displayName": "rating",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "reviewCount",
"type": "string",
"display": true,
"required": false,
"displayName": "reviewCount",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "valueProposition",
"type": "string",
"display": true,
"required": false,
"displayName": "valueProposition",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "painPoints",
"type": "string",
"display": true,
"required": false,
"displayName": "painPoints",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "outreachHook",
"type": "string",
"display": true,
"required": false,
"displayName": "outreachHook",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "leadScore",
"type": "string",
"display": true,
"required": false,
"displayName": "leadScore",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "engagementStrategy",
"type": "string",
"display": true,
"required": false,
"displayName": "engagementStrategy",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "googleMaprUrl",
"type": "string",
"display": true,
"required": false,
"displayName": "googleMaprUrl",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "scrapedAt",
"type": "string",
"display": true,
"required": false,
"displayName": "scrapedAt",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "enrichedAt",
"type": "string",
"display": true,
"required": false,
"displayName": "enrichedAt",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "outreachMessage",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "outreachMessage",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "status",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "status",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1on82bf0niAySDtRUfXrhFR8aTcrVGdgCDZS5L1RAvcs/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1on82bf0niAySDtRUfXrhFR8aTcrVGdgCDZS5L1RAvcs",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1on82bf0niAySDtRUfXrhFR8aTcrVGdgCDZS5L1RAvcs/edit?usp=drivesdk",
"cachedResultName": "Google Maps Outreach"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "f480c7b4-1c3b-46ed-af0d-c34a00e7bbf8",
"name": "Filter Hot Leads",
"type": "n8n-nodes-base.if",
"notes": "Filters for high-quality leads (score \u22657) with contact info for immediate outreach.",
"position": [
-800,
320
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "filter-high-quality",
"operator": {
"type": "number",
"operation": "gte"
},
"leftValue": "={{ $json.leadScore }}",
"rightValue": 7
},
{
"id": "has-contact",
"operator": {
"type": "string",
"operation": "notEmpty"
},
"leftValue": "={{ $json.phone || $json.website }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2
},
{
"id": "e4efe4ba-94d7-4596-85d0-ef04972884a0",
"name": "Error Handler",
"type": "n8n-nodes-base.errorTrigger",
"position": [
-3024,
672
],
"parameters": {},
"typeVersion": 1
},
{
"id": "a148649b-de2f-4709-a9ed-b586aa71d595",
"name": "Result Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
-1440,
384
],
"parameters": {
"jsonSchemaExample": "{\n \"valueProposition\": \"...\",\n \"painPoints\": [\"...\", \"...\", \"...\"],\n \"outreachHook\": \"...\",\n \"leadScore\": 8,\n \"engagementStrategy\": \"...\",\n \"reasoning\": \"...\"\n}"
},
"typeVersion": 1.3
},
{
"id": "2974dcdf-0ff7-42d3-b5cb-3ce094f6b45b",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2496,
224
],
"parameters": {
"color": 7,
"width": 528,
"height": 304,
"content": "## Data Collection\n\nScrapes Google Maps via Decodo Maps Scrapper API, then parses HTML into structured data including business details, contact info, and coordinates."
},
"typeVersion": 1
},
{
"id": "83a319fe-e5ab-4c1b-9744-60faf5ae357f",
"name": "2.5 Flash",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
-1600,
384
],
"parameters": {
"options": {}
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "be7d26d0-b124-486e-bb16-02cd8537ea6d",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3072,
560
],
"parameters": {
"color": 7,
"width": 688,
"height": 288,
"content": "## Error Handling\n\nCatches workflow failures, formats error details, sends Telegram alerts to admin."
},
"typeVersion": 1
},
{
"id": "39612ec8-37a8-489a-ba6a-983a95aece58",
"name": "Lead Enrichment",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
-1600,
256
],
"parameters": {
"text": "=You are an expert B2B lead enrichment analyst specializing in local business intelligence. Your task is to analyze business data and create actionable insights for cold outreach campaigns.\n\nBUSINESS DATA:\n- Name: {{ $json.businessName }}\n- Category: {{ $json.category }}\n- Rating: {{ $json.rating }}/5 ({{ $json.reviewCount }} reviews)\n- Website: {{ $json.website || 'No website available' }}\n- Location: {{ $json.address }}\n- Description: {{ $json.description || 'No description available' }}\n- Phone: {{ $json.phone || 'Not provided' }}\n- Opening Hours: {{ $json.openingHours || 'Not available' }}\n\nANALYSIS REQUIREMENTS:\n\n1. VALUE PROPOSITION (2-3 sentences):\n - Create a compelling, personalized value proposition for cold outreach\n - Focus on specific, measurable benefits relevant to their business category\n - Address their likely business goals (growth, efficiency, customer retention)\n - Make it conversational and not overly salesy\n\n2. PAIN POINTS (Exactly 3 items):\n - Identify three specific operational or business challenges they likely face\n - Base this on their category, location, online presence, and customer feedback indicators\n - Consider: digital transformation needs, competitive pressure, operational inefficiencies, customer acquisition costs, retention issues\n - Be specific and actionable, not generic\n\n3. OUTREACH HOOK (1-2 sentences):\n - Write a compelling opening line for a cold email that immediately captures attention\n - Reference specific details from their business (rating, reviews, location, category)\n - Create curiosity or demonstrate value without being pushy\n - Avoid clich\u00e9s like \"I noticed you don't have...\" or \"Are you tired of...\"\n - Make it about THEM, not your product\n\n4. LEAD SCORE (1-10 scale):\n Calculate based on these weighted criteria:\n - Business reputation: Rating \u22654.5 stars (+2), 4.0-4.4 (+1), <4.0 (0)\n - Social proof: Reviews \u2265100 (+2), 50-99 (+1.5), 20-49 (+1), <20 (0)\n - Digital maturity: Professional website (+2), basic website (+1), no website (0)\n - Contact availability: Phone + website (+1), phone only (+0.5), neither (-1)\n - Market position: High-end category/location (+1), mid-market (+0.5), budget (0)\n - Engagement potential: Active business with recent reviews (+1), stagnant (0)\n \n CRITICAL: If missing critical data (name unknown, no contact info), cap score at 5\n\n5. ENGAGEMENT STRATEGY (2-3 sentences):\n - Recommend the best initial contact method: email, phone call, LinkedIn, or in-person visit\n - Specify timing recommendations (best day/time to reach out)\n - Suggest follow-up approach and cadence\n - Consider their business type, location, and decision-maker accessibility\n\n6. REASONING (1-2 sentences):\n - Briefly explain the lead score rationale\n - Highlight the strongest opportunity or biggest challenge\n - Note any data gaps that might affect assessment accuracy\n\nOUTPUT FORMAT:\nReturn ONLY valid JSON with no additional text, markdown, or explanations:\n{\n \"valueProposition\": \"string (2-3 compelling sentences)\",\n \"painPoints\": [\"string (specific pain point 1)\", \"string (specific pain point 2)\", \"string (specific pain point 3)\"],\n \"outreachHook\": \"string (1-2 attention-grabbing sentences)\",\n \"leadScore\": number (integer 1-10),\n \"engagementStrategy\": \"string (2-3 sentences with specific recommendations)\",\n \"reasoning\": \"string (1-2 sentences explaining score and opportunities)\"\n}\n\nIMPORTANT GUIDELINES:\n- Be specific and actionable, avoid generic business advice\n- Tailor everything to their specific business category and location\n- If data is incomplete, acknowledge uncertainty in reasoning but still provide useful insights\n- Focus on what you CAN determine rather than what's missing\n- Use natural language that sounds like a knowledgeable consultant, not a robot\n- Never fabricate information - if you don't have data, say so in reasoning but provide educated estimates",
"batching": {},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.7
},
{
"id": "a2406796-1658-428a-aeec-f88320a6fe3c",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1056,
112
],
"parameters": {
"color": 7,
"width": 848,
"height": 432,
"content": "## Hot Leads Processing\n\nSaves all enriched leads to Sheets. Filters high-quality prospects (score \u22657 with contact info), generates personalized email templates, updates status as \"HOT\"."
},
"typeVersion": 1
},
{
"id": "a292f2b0-da79-40dc-8049-3be70559b52f",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1936,
112
],
"parameters": {
"color": 7,
"width": 848,
"height": 432,
"content": "## AI Enrichment Loop\n\nProcesses leads in batches. Gemini 2.5 Flash analyzes each business to generate value propositions, identify pain points, create outreach hooks, and assign quality scores (1-10)."
},
"typeVersion": 1
},
{
"id": "a814d273-c43d-46b5-92f0-387f68b8240f",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2352,
560
],
"parameters": {
"color": 6,
"width": 528,
"height": 288,
"content": "## Create Decodo Credentials\nService: Decodo Maps Scrapper\nNode: HTTP Request\nURL: https://scraper-api.decodo.com/v2/scrape\nCredential Type: HTTP Header Auth\nHeader Name: Authorization\nHeader Value: Basic [YOUR_DECODO_API_KEY]\n\nGet API key: https://dashboard.decodo.com/web-scraping-api/scraper?target=google_maps\n\n"
},
"typeVersion": 1
},
{
"id": "40f012cf-ffcb-4ab8-b375-1c58a8bac292",
"name": "Decodo Maps Scraper",
"type": "n8n-nodes-base.httpRequest",
"notes": "Scrapes Google Maps using Decodo API. Set up HTTP Header Auth with your API key.",
"position": [
-2400,
368
],
"parameters": {
"url": "https://scraper-api.decodo.com/v2/scrape",
"method": "POST",
"options": {
"timeout": 120000
},
"sendBody": true,
"sendHeaders": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "target",
"value": "google_maps"
},
{
"name": "query",
"value": "={{ $json.searchQuery }}"
},
{
"name": "page_from",
"value": "1"
},
{
"name": "headless",
"value": "html"
},
{
"name": "google_results_language",
"value": "={{ $json.targetLanguage }}"
},
{
"name": "geo",
"value": "={{ $json.country }}"
},
{
"name": "page_to",
"value": "={{ Math.ceil(parseInt($json.resultsLimit) / 10) }}"
}
]
},
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/json"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "7b927ca8-69f0-4565-bc1b-631e52b0f0f3",
"name": "Merge Enrichment Data",
"type": "n8n-nodes-base.set",
"position": [
-1248,
256
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "0a0809e9-db9d-48d9-813f-e2d9d4d8f1cb",
"name": "id",
"type": "string",
"value": "={{ $('Split Into Batches').item.json.id }}"
},
{
"id": "f0e5fc35-1303-4db8-ab31-dc0015e82eb9",
"name": "businessName",
"type": "string",
"value": "={{ $('Split Into Batches').item.json.businessName }}"
},
{
"id": "63d25c3c-6274-4dee-bf7c-f8fefe8dd33e",
"name": "category",
"type": "string",
"value": "={{ $('Split Into Batches').item.json.category }}"
},
{
"id": "651f590e-c256-4cf3-8f53-e44c23d0f7c7",
"name": "valueProposition",
"type": "string",
"value": "={{ $json.output.valueProposition }}"
},
{
"id": "eeeb5842-be49-4596-9a4a-a03af7d1169b",
"name": "painPoints",
"type": "string",
"value": "={{ $json.output.painPoints[0] }}"
},
{
"id": "14db2aca-8d00-48b2-80c7-b832c4c8070b",
"name": "outreachHook",
"type": "string",
"value": "={{ $json.output.outreachHook }}"
},
{
"id": "1f10742c-cc68-46fb-a2bb-882d17e06ff4",
"name": "leadScore",
"type": "number",
"value": "={{ $json.output.leadScore }}"
},
{
"id": "2ad4e2ed-67b1-42ce-9fea-6982905e3707",
"name": "engagementStrategy",
"type": "string",
"value": "={{ $json.output.engagementStrategy }}"
},
{
"id": "87ff456c-ef7a-4c21-9e1d-b2bd71d7b6a0",
"name": "reasoning",
"type": "string",
"value": "={{ $json.output.reasoning }}"
},
{
"id": "89e1d25e-61ee-46a2-95d4-cc4c34671d32",
"name": "address",
"type": "string",
"value": "={{ $('Split Into Batches').item.json.address }}"
},
{
"id": "fae08fca-6227-45b6-9002-516057b4f237",
"name": "phone",
"type": "string",
"value": "={{ $('Split Into Batches').item.json.phone }}"
},
{
"id": "3aaf1d60-bf59-4ef3-8e90-2fd57cf0a605",
"name": "website",
"type": "string",
"value": "={{ $('Split Into Batches').item.json.website }}"
},
{
"id": "dc6ebf0a-5e43-4f8e-abe6-404d16f6d995",
"name": "rating",
"type": "number",
"value": "={{ $('Split Into Batches').item.json.rating }}"
},
{
"id": "1f17191e-e5b4-4e7a-90b0-1d7f9a12f902",
"name": "reviewCount",
"type": "string",
"value": "={{ $('Split Into Batches').item.json.reviewCount }}"
},
{
"id": "f76837b3-734d-48d3-86d4-77e6081c9431",
"name": "googleMapsUrl",
"type": "string",
"value": "={{ $('Split Into Batches').item.json.googleMapsUrl }}"
},
{
"id": "8bd36ee5-bd43-4da6-8409-e3a6039a4e1d",
"name": "scrapedAt",
"type": "string",
"value": "={{ $('Split Into Batches').item.json.scrapedAt.toDateTime().format('yyyy-MM-dd HH:mm:ss') }}"
},
{
"id": "a3b4d978-a640-4925-8490-7abb5aa73867",
"name": "statusOutreach",
"type": "string",
"value": "not send"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "da4b3434-5fba-4c5d-b239-4770286acdbd",
"name": "Prepare Outreach Message",
"type": "n8n-nodes-base.set",
"notes": "Prepares personalized outreach message using AI-generated hooks.",
"position": [
-592,
336
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "email-subject",
"name": "emailSubject",
"type": "string",
"value": "=Quick question about {{ $json.businessName }}"
},
{
"id": "email-body",
"name": "emailBody",
"type": "string",
"value": "=Hi {{ $json.businessName }} team,\n\n{{ $json.outreachHook }}\n\n{{ $json.valueProposition }}\n\nWould you be open to a 15-minute chat this week?\n\nBest regards,\n[Your Name]\n[Your Company]"
},
{
"id": "recipient",
"name": "recipientEmail",
"type": "string",
"value": "={{ $json.email || 'MANUAL_LOOKUP_NEEDED' }}"
},
{
"id": "17aea5ed-3c5c-40ec-8ecf-cb1db7eb5a52",
"name": "leadScore",
"type": "string",
"value": "={{ $json.leadScore }}"
},
{
"id": "fbaf37ba-95a3-4ff5-8a11-18633831d6ba",
"name": "id",
"type": "string",
"value": "={{ $json.id }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "919d9446-e033-42b6-a2fe-8eb7f0180b11",
"name": "Save Outreach To Sheets",
"type": "n8n-nodes-base.googleSheets",
"notes": "Saves enriched leads to Google Sheets. Use appendOrUpdate to avoid duplicates.",
"position": [
-400,
336
],
"parameters": {
"columns": {
"value": {
"id": "={{ $json.id }}",
"enrichedAt": "={{ $now.toDateTime().format('yyyy-MM-dd HH:mm:ss') }}",
"leadsCategory": "HOT",
"outreachMessage": "=subject: {{ $json.emailSubject }}\n\n{{ $json.emailBody }}\n"
},
"schema": [
{
"id": "id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "id",
"defaultMatch": true,
"canBeUsedToMatch": true
},
{
"id": "leadsCategory",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "leadsCategory",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "businessName",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "businessName",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "category",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "category",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "address",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "address",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "phone",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "website",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "website",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "rating",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "rating",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "reviewCount",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "reviewCount",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "valueProposition",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "valueProposition",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "painPoints",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "painPoints",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "outreachHook",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "outreachHook",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "leadScore",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "leadScore",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "engagementStrategy",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "engagementStrategy",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "googleMaprUrl",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "googleMaprUrl",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "scrapedAt",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "scrapedAt",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "enrichedAt",
"type": "string",
"display": true,
"required": false,
"displayName": "enrichedAt",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "outreachMessage",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "outreachMessage",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "status",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "status",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1on82bf0niAySDtRUfXrhFR8aTcrVGdgCDZS5L1RAvcs/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1on82bf0niAySDtRUfXrhFR8aTcrVGdgCDZS5L1RAvcs",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1on82bf0niAySDtRUfXrhFR8aTcrVGdgCDZS5L1RAvcs/edit?usp=drivesdk",
"cachedResultName": "Google Maps Outreach"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "8e072a72-0042-4fef-912f-fe2a4ac1fc90",
"name": "Format Error Message",
"type": "n8n-nodes-base.code",
"position": [
-2800,
672
],
"parameters": {
"jsCode": "// Optimized error handler - clean & informative\nconst errorData = $input.first().json;\n\nfunction getNestedValue(obj, path, defaultValue = 'Unknown') {\n return path.split('.').reduce((curr, prop) => \n curr?.[prop], obj) || defaultValue;\n}\n\nconst workflowName = getNestedValue(errorData, 'workflow.name', 'Unknown Workflow');\nconst nodeName = getNestedValue(errorData, 'execution.error.node.name', 'Unknown Node');\nconst errorMessage = getNestedValue(errorData, 'execution.error.message', 'No error message');\nconst timestamp = getNestedValue(errorData, 'execution.startedAt', new Date().toISOString());\nconst executionId = getNestedValue(errorData, 'execution.id', 'Unknown');\n\n// Tangkap error details\nconst errorObj = errorData.execution?.error || {};\nconst errorDescription = errorObj.description || '';\nconst errorHttpCode = errorObj.httpCode || '';\n\n// Stack trace - ambil HANYA baris pertama yang paling penting\nconst errorStack = errorObj.stack || '';\nconst mainError = errorStack.split('\\n')[0] || '';\n\n// Format detail yang clean\nlet errorDetails = [];\nif (errorDescription && errorDescription !== errorMessage) {\n errorDetails.push(errorDescription);\n}\nif (errorHttpCode) {\n errorDetails.push(`HTTP ${errorHttpCode}`);\n}\n\nconst detailText = errorDetails.length > 0 \n ? errorDetails.join(' | ') \n : 'No additional details';\n\n// Main error dari stack (tanpa \"NodeApiError: \" prefix)\nconst cleanMainError = mainError.replace(/^(NodeApiError|Error):\\s*/, '').trim();\n\n// Build message - COMPACT VERSION\nconst message = `\ud83d\udea8 <b>WORKFLOW ERROR</b>\\n\\n` +\n `\ud83d\udccb <b>${workflowName}</b>\\n` +\n `\u2699\ufe0f Node: <code>${nodeName}</code>\\n\\n` +\n `\u274c <b>${errorMessage}</b>\\n` +\n `\ud83d\udca1 ${detailText}\\n\\n` +\n `\ud83d\udd50 ${new Date(timestamp).toLocaleString('id-ID', { \n day: '2-digit', \n month: 'short', \n hour: '2-digit', \n minute: '2-digit' \n })}\\n` +\n `\ud83d\udd17 Exec: <code>${executionId}</code>`;\n\nreturn {\n json: {\n message: message\n }\n};"
},
"typeVersion": 2
},
{
"id": "2ea4e411-5e49-4d4f-8cf4-ba6dd4868912",
"name": "Send Error Notification",
"type": "n8n-nodes-base.telegram",
"position": [
-2576,
672
],
"parameters": {
"text": "={{ $json.message }}",
"chatId": "YOUR-CHAT-ID",
"additionalFields": {
"parse_mode": "HTML"
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "1dc9988f-ff99-46b7-9558-c94e41be740a",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3072,
-368
],
"parameters": {
"width": 1104,
"height": 544,
"content": "# Workflow Overview\n## How it works\n\nScrapes Google Maps via Decodo API, enriches each business with AI Agent, scores lead quality (1-10), and generates personalized outreach emails. Hot leads (score \u22657) get saved to Google Sheets with ready to send messages.\n\n## Setup steps\n\n1. **Decodo API:** Get key from [decodo.com](https://dashboard.decodo.com/web-scraping-api/scraper?target=google_maps) Add as HTTP Header Auth credential (Authorization: Basic YOUR_KEY)\n\n2. **Google Gemini:** Add API key in \"Gemini 2.5 Flash\" from ai.dev\n\n3. **Configure search:** Update \"Set Search Parameters\" with your query (e.g., \"coffee shops in Austin\"), country, and resultsLimit\n\n4. **Test:** Start with resultsLimit: 5 to verify connections\n\n5. **Optional:** Add Telegram bot token for error notifications"
},
"typeVersion": 1
},
{
"id": "052457f2-99d4-4291-9122-584615f68992",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-2992,
368
],
"parameters": {},
"typeVersion": 1
},
{
"id": "b108d3b9-4224-452d-a89a-ce007761cc40",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2832,
224
],
"parameters": {
"color": 7,
"width": 304,
"height": 304,
"content": "## Input Configuration\n\nSet search query, target country, language, and results limit. Adjust these parameters for different markets or niches."
},
"typeVersion": 1
},
{
"id": "9095d650-d056-4406-a787-a78187e842a9",
"name": "Set Search Parameters",
"type": "n8n-nodes-base.set",
"notes": "Configure your search query here. Update searchQuery for different niches.",
"position": [
-2736,
368
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "95a5b940-2ea4-4511-80d5-237a33d9c411",
"name": "searchQuery",
"type": "string",
"value": "YOUR-QUERY_HERE"
},
{
"id": "a6a2004b-daa2-43e1-ad8c-e0b51f42c272",
"name": "targetLanguage",
"type": "string",
"value": "en"
},
{
"id": "b4abd52f-16a4-492c-bec7-a95fadfce2ba",
"name": "country",
"type": "string",
"value": "Country Name"
},
{
"id": "757c413f-7385-4de5-8368-9bc4016e40a3",
"name": "resultsLimit",
"type": "number",
"value": 5
}
]
}
},
"typeVersion": 3.4
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "3e060442-edc4-4b19-97e2-c8cdf39404d9",
"connections": {
"2.5 Flash": {
"ai_languageModel": [
[
{
"node": "Lead Enrichment",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Error Handler": {
"main": [
[
{
"node": "Format Error Message",
"type": "main",
"index": 0
}
]
]
},
"Result Parser": {
"ai_outputParser": [
[
{
"node": "Lead Enrichment",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Manual Trigger": {
"main": [
[
{
"node": "Set Search Parameters",
"type": "main",
"index": 0
}
]
]
},
"Lead Enrichment": {
"main": [
[
{
"node": "Merge Enrichment Data",
"type": "main",
"index": 0
}
]
]
},
"Filter Hot Leads": {
"main": [
[
{
"node": "Prepare Outreach Message",
"type": "main",
"index": 0
}
],
[
{
"node": "Split Into Batches",
"type": "main",
"index": 0
}
]
]
},
"Split Into Batches": {
"main": [
[],
[
{
"node": "Lead Enrichment",
"type": "main",
"index": 0
}
]
]
},
"Decodo Maps Scraper": {
"main": [
[
{
"node": "Parse & Normalize Data",
"type": "main",
"index": 0
}
]
]
},
"Format Error Message": {
"main": [
[
{
"node": "Send Error Notification",
"type": "main",
"index": 0
}
]
]
},
"Merge Enrichment Data": {
"main": [
[
{
"node": "Save to Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"Save to Google Sheets": {
"main": [
[
{
"node": "Filter Hot Leads",
"type": "main",
"index": 0
}
]
]
},
"Set Search Parameters": {
"main": [
[
{
"node": "Decodo Maps Scraper",
"type": "main",
"index": 0
}
]
]
},
"Parse & Normalize Data": {
"main": [
[
{
"node": "Split Into Batches",
"type": "main",
"index": 0
}
]
]
},
"Save Outreach To Sheets": {
"main": [
[
{
"node": "Split Into Batches",
"type": "main",
"index": 0
}
]
]
},
"Prepare Outreach Message": {
"main": [
[
{
"node": "Save Outreach To Sheets",
"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.
googlePalmApigoogleSheetsOAuth2ApihttpHeaderAuthtelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow scrapes Google Maps via Decodo API, analyzes each business using Google Gemini 3 Flash, scores lead quality, and generates ready to send outreach emails. Time Savings: Reduces manual lead research from 20 minutes per lead to 30 seconds processing 100 leads in under…
Source: https://n8n.io/workflows/10760/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow creates a multi-talented AI assistant named Simran that interacts with users via Telegram. It can handle text and voice messages, understand the user's intent, and perform various tasks.
Find trending theories – Uses Grok-4 to scan X (Twitter) for the top emerging conspiracy theory from the last 3 days Write the script – Takes the theory and creates a 24-second documentary-style scrip
Generate the script – Takes a "what if" question and creates a 24-second video script with 4 scenes (Hook, Wonder, Reality, CTA) using Gemini 2.5 Pro Create Veo prompts – Translates each scene's visua
This automation is designed to help you generate AI-powered music tracks, cover art, and fully rendered music videos — all triggered from a simple Telegram chat and managed via Google Sheets.
LinkedIn URL → Scrape → Match → Screen → Decide, all automated