AutomationFlowsMarketing & Ads › Lead Research & Enrichment Pipeline

Lead Research & Enrichment Pipeline

Lead Research & Enrichment Pipeline. Uses googleSheets, readWriteFile, whatsApp. Scheduled trigger; 11 nodes.

Cron / scheduled trigger★★★★☆ complexity11 nodesGoogle SheetsRead Write FileWhatsApp
Marketing & Ads Trigger: Cron / scheduled Nodes: 11 Complexity: ★★★★☆ Added:

This workflow follows the Google Sheets → WhatsApp 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 →

Download .json
{
  "name": "Lead Research & Enrichment Pipeline",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "30 8 * * *"
            }
          ]
        }
      },
      "id": "node-schedule",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [
        80,
        176
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "lead-scraper-done",
        "options": {}
      },
      "id": "scraper-webhook-trigger",
      "name": "Scraper Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        80,
        -16
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "YOUR_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Leads"
        },
        "options": {}
      },
      "id": "node-read-phones",
      "name": "Read Existing Phones",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        304,
        128
      ],
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "fileSelector": "/home/node/.n8n-files/leads-latest.csv",
        "options": {}
      },
      "id": "node-readfile",
      "name": "Read CSV File",
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1.1,
      "position": [
        528,
        128
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "node-extract-csv",
      "name": "Extract from File",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1,
      "position": [
        752,
        128
      ]
    },
    {
      "parameters": {
        "jsCode": "function normalizePhone(raw) {\n  const digits = (raw || '').toString().replace(/\\D/g, '').trim();\n  if (!digits || digits.length < 7) return null;\n  if ((digits.startsWith('91') || digits.startsWith('0')) && digits.length >= 11)\n    return digits.slice(-10);\n  return digits;\n}\n\nconst existingItems = $('Read Existing Phones').all();\nconst existingPhones = new Set();\nfor (const item of existingItems) {\n  const p = normalizePhone(item.json.phone || item.json.E || '');\n  if (p) existingPhones.add(p);\n}\n\nconst staticData = $getWorkflowStaticData('global');\nconst runPhones = staticData.runPhones || [];\nfor (const p of runPhones) existingPhones.add(p);\n\nconst allItems = $input.all();\nconst result = [];\nconst newPhones = [];\n\nfor (let i = 0; i < allItems.length; i++) {\n  const item = allItems[i];\n  const p = normalizePhone(item.json.phone || '');\n  if (!p) continue;\n  if (existingPhones.has(p)) continue;\n  existingPhones.add(p);\n  newPhones.push(p);\n  result.push({ json: item.json, pairedItem: { item: i } });\n}\n\nstaticData.runPhones = [...runPhones, ...newPhones];\nreturn result;"
      },
      "id": "node-filter-dedup",
      "name": "Filter Duplicates",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        976,
        128
      ]
    },
    {
      "parameters": {
        "jsCode": "const allItems = $input.all();\nconst result = [];\n\nfor (let i = 0; i < allItems.length; i++) {\n  const row = allItems[i].json;\n\n  const rawPhone = (row.phone || '').toString().replace(/\\D/g, '');\n  const phone = rawPhone.startsWith('91') && rawPhone.length > 10 ? rawPhone.slice(2) : rawPhone;\n\n  const email = row.emails || '';\n  const outreach_channel = email ? 'email' : (phone ? 'whatsapp' : 'instagram');\n\n  const city = row.city || '';\n\n  const now = new Date(new Date().toLocaleString('en-US', { timeZone: 'Asia/Kolkata' }));\n  const dd = String(now.getDate()).padStart(2, '0');\n  const mm = String(now.getMonth() + 1).padStart(2, '0');\n  const yyyy = now.getFullYear();\n  const scraped_date = `${yyyy}-${mm}-${dd}`;\n\n  result.push({\n    json: {\n      business_name: row.title || '',\n      owner_name: '',\n      city,\n      vertical: row.category || '',\n      phone: phone || '',\n      whatsapp_valid: 'pending',\n      email,\n      website: row.website || '',\n      instagram_handle: '',\n      linkedin_url: '',\n      rating: row.review_rating || '',\n      review_count: row.review_count || '',\n      lead_score: '',\n      outreach_status: 'new',\n      outreach_channel,\n      scraped_date,\n      first_contacted_date: '',\n      first_message_sent: '',\n      first_reply: '',\n      followup_1_date: '',\n      followup_1_message: '',\n      followup_1_reply: '',\n      followup_2_date: '',\n      followup_2_message: '',\n      followup_2_reply: '',\n      interested: '',\n      demo_booked: '',\n      demo_date: '',\n      notes: row.address || ''\n    },\n    pairedItem: { item: i }\n  });\n}\n\nconst staticData = $getWorkflowStaticData('global');\nstaticData.leadsAddedCount = (staticData.leadsAddedCount || 0) + result.length;\n\nreturn result;"
      },
      "id": "node-prepare-leads",
      "name": "Prepare Leads",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1200,
        128
      ]
    },
    {
      "parameters": {
        "batchSize": 10,
        "options": {}
      },
      "id": "node-batches",
      "name": "Split In Batches",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        1424,
        112
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Leads"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "business_name": "={{ $json.business_name }}",
            "city": "={{ $json.city }}",
            "vertical": "={{ $json.vertical }}",
            "phone": "={{ $json.phone }}",
            "email": "={{ $json.email }}",
            "website": "={{ $json.website }}",
            "rating": "={{ $json.rating }}",
            "review_count": "={{ $json.review_count }}",
            "scraped_date": "={{ $json.scraped_date }}",
            "outreach_status": "={{ $json.outreach_status }}",
            "outreach_channel": "={{ $json.outreach_channel }}",
            "notes": "={{ $json.notes }}"
          },
          "matchingColumns": [],
          "schema": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false,
          "valueInputOption": "USER_ENTERED"
        },
        "options": {}
      },
      "id": "node-sheets",
      "name": "Save to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1648,
        176
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const staticData = $getWorkflowStaticData('global');\nconst count = staticData.leadsAddedCount || 0;\n\nstaticData.leadsAddedCount = 0;\nstaticData.runPhones = [];\n\nconst now = new Date();\nconst dateStr = now.toLocaleDateString('en-IN', {\n  timeZone: 'Asia/Kolkata',\n  weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'\n});\nconst timeStr = now.toLocaleTimeString('en-IN', {\n  timeZone: 'Asia/Kolkata', hour: '2-digit', minute: '2-digit', hour12: true\n});\n\nconst message = `Lead Research Complete!\\n\\nDate: ${dateStr}\\nRun at: ${timeStr} IST\\n\\nNew leads added: ${count}`;\n\nreturn [{ json: { message } }];"
      },
      "id": "node-wa-summary-body",
      "name": "Build WA Summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1648,
        -16
      ]
    },
    {
      "parameters": {
        "operation": "send",
        "phoneNumberId": "={{$env.WHATSAPP_PHONE_NUMBER_ID}}",
        "recipientPhoneNumber": "={{$env.ALERT_PHONE_NUMBER}}",
        "textBody": "={{ $json.message }}",
        "additionalFields": {}
      },
      "type": "n8n-nodes-base.whatsApp",
      "typeVersion": 1.1,
      "position": [
        1872,
        -16
      ],
      "id": "node-send-summary",
      "name": "Send WhatsApp Summary"
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Read Existing Phones",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scraper Webhook": {
      "main": [
        [
          {
            "node": "Read Existing Phones",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Existing Phones": {
      "main": [
        [
          {
            "node": "Read CSV File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read CSV File": {
      "main": [
        [
          {
            "node": "Extract from File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract from File": {
      "main": [
        [
          {
            "node": "Filter Duplicates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Duplicates": {
      "main": [
        [
          {
            "node": "Prepare Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Leads": {
      "main": [
        [
          {
            "node": "Split In Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split In Batches": {
      "main": [
        [
          {
            "node": "Build WA Summary",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Save to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save to Google Sheets": {
      "main": [
        [
          {
            "node": "Split In Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build WA Summary": {
      "main": [
        [
          {
            "node": "Send WhatsApp Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "Asia/Kolkata",
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all",
    "saveManualExecutions": true
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Lead Research & Enrichment Pipeline. Uses googleSheets, readWriteFile, whatsApp. Scheduled trigger; 11 nodes.

Source: https://gist.github.com/Aakib-Khan/7eb5907d14baed219c8de3e733bf0332 — original creator credit. Request a take-down →

More Marketing & Ads workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Marketing & Ads

SMS+Mail Campaña - WF2 Envio Mail (30 min despues del SMS). Uses googleSheets, readWriteFile, emailSend. Scheduled trigger; 18 nodes.

Google Sheets, Read Write File, Email Send
Marketing & Ads

This workflow runs on scheduled weekly and monthly triggers to generate unified marketing performance reports. It processes multiple websites by collecting analytics data, paid ads performance, and CR

Gmail, Google Sheets, Google Analytics +3
Marketing & Ads

Goal: Get Reddit posts from specific subreddits, filter those mentioning freelance/gigs and n8n, extract top-level comments, remove mod replies, and store everything into Google Sheets.

HTTP Request, Reddit, Google Sheets
Marketing & Ads

This workflow acts as an instant SDR that replies to new inbound leads across multiple channels in real time. It first captures and normalizes all incoming lead data into a unified structure. The work

Google Sheets, HTTP Request, Gmail +1
Marketing & Ads

Outreach Automation. Uses googleSheets, n8n-nodes-hdw. Scheduled trigger; 25 nodes.

Google Sheets, N8N Nodes Hdw