AutomationFlowsAI & RAG › Find Leads on Google Maps and Reach Out Automatically (gpt-4 + Airtable + Gmail)

Find Leads on Google Maps and Reach Out Automatically (gpt-4 + Airtable + Gmail)

ByBaptiste Fort @baptistefort on n8n.io

This workflow is perfect for marketers, sales teams, agencies, and local businesses who want to save time by automating lead generation from Google Maps.

Event trigger★★★★☆ complexityAI-powered19 nodesHTTP RequestOpenAIAirtableGmail
AI & RAG Trigger: Event Nodes: 19 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #5811 — we link there as the canonical source.

This workflow follows the Airtable → Gmail 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
{
  "id": "WwTxOC2aBSx7azd8",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Find leads on Google Maps and reach out automatically GPT-4+Airtable+Gmail",
  "tags": [],
  "nodes": [
    {
      "id": "ca87077f-4fe9-4c15-955e-57aac8d40fec",
      "name": "Fetch Business Data from Google Maps (Apify)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        240,
        0
      ],
      "parameters": {
        "url": "https://api.apify.com/v2/acts/compass~crawler-google-places/run-sync-get-dataset-items?token=YOUR_TOKEN_HERE",
        "method": "POST",
        "options": {},
        "jsonBody": "{\n    \"includeWebResults\": false,\n    \"language\": \"fr\",\n    \"locationQuery\": \"Paris, France\",\n    \"maxCrawledPlacesPerSearch\": 5000,\n    \"maxImages\": 0,\n    \"maximumLeadsEnrichmentRecords\": 0,\n    \"scrapeContacts\": false,\n    \"scrapeDirectories\": false,\n    \"scrapeImageAuthors\": false,\n    \"scrapePlaceDetailPage\": false,\n    \"scrapeReviewsPersonalData\": true,\n    \"scrapeTableReservationProvider\": false,\n    \"searchStringsArray\": [\n        \"centre de formation\"\n    ],\n    \"skipClosedPlaces\": false\n}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "ba6cc087-750f-424a-8707-c57c3a38a897",
      "name": "Clean Google Maps Data",
      "type": "n8n-nodes-base.set",
      "position": [
        512,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "e0ee1e00-da28-49c7-b8ad-b1cf983b1004",
              "name": "Nom du centre de formation",
              "type": "string",
              "value": "={{ $json.title }}"
            },
            {
              "id": "a1d6f737-2c45-4c90-950e-4ef356f788bd",
              "name": "Cat\u00e9gorie",
              "type": "string",
              "value": "={{ $json.categoryName }}"
            },
            {
              "id": "9ff0e712-4f36-4006-86c5-1d40c5675c4a",
              "name": "Adresse",
              "type": "string",
              "value": "={{ $json.address }}"
            },
            {
              "id": "b53c8805-9b69-498b-9347-b0d4fca6099c",
              "name": "Ville",
              "type": "string",
              "value": "={{ $json.city }}"
            },
            {
              "id": "87573cd5-3f99-4712-ba2c-260ddc6f43b2",
              "name": "Code postale",
              "type": "string",
              "value": "={{ $json.postalCode }}"
            },
            {
              "id": "e4fb0044-38d5-4fe5-a9ee-1da98c34567c",
              "name": "Site internet",
              "type": "string",
              "value": "={{ $json.website }}"
            },
            {
              "id": "c23a3ba2-9e62-4d77-a6c9-afc0b43235ad",
              "name": "Num\u00e9ro de t\u00e9l\u00e9phone",
              "type": "string",
              "value": "={{ $json.phone }}"
            },
            {
              "id": "7d4a6a71-d01f-4a7d-82ce-a8ac5396c87a",
              "name": "Nombre d'avis",
              "type": "string",
              "value": "={{ $json.reviewsCount }}"
            },
            {
              "id": "29994cd7-d775-480f-9901-6269dc2a4787",
              "name": "Note sur 5",
              "type": "string",
              "value": "={{ $json.totalScore }}"
            },
            {
              "id": "6de303d2-3d35-4458-b4cc-ab1ec09929e4",
              "name": "Lien google",
              "type": "string",
              "value": "={{ $json.url }}"
            },
            {
              "id": "d1c7cc55-55f0-4e66-a00f-415a2c69b743",
              "name": "",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "0e63ff27-e62f-4161-9015-177f5cec970e",
      "name": "Manual Workflow Execution",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        0,
        0
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "06ea1c9e-e38c-471e-8a19-54ef9e637dc0",
      "name": "Iterate Through Each Business Contact",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        752,
        0
      ],
      "parameters": {
        "options": {},
        "batchSize": 10
      },
      "typeVersion": 3
    },
    {
      "id": "90db8934-379e-4199-963d-e0dbf255f2f7",
      "name": "Extract Only Website URLs",
      "type": "n8n-nodes-base.set",
      "position": [
        1024,
        16
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ccc11066-fad4-4931-acf6-6c45e5e7b117",
              "name": "Site internet",
              "type": "string",
              "value": "={{ $json['Site internet'] }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "fb9f0823-6d37-4633-b255-fc60810fbc66",
      "name": "Fetch Raw HTML Content from Business Website",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1264,
        16
      ],
      "parameters": {
        "url": "={{ $json['Site internet'] }}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "59ccca05-d725-4a87-a07d-5cc59698266f",
      "name": "Extract Business Email from Website HTML (GPT-4)",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1472,
        16
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini",
          "cachedResultName": "GPT-4.1-MINI"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=Look at this website content and extract only the email I can contact this business. In your output, provide only the email and nothing else. Ideally, this email should be of the business owner, so if you have 2 or more options, try for most authoritative one. If you don't find any email, output 'Null'.\n\nExemplary output of yours:\n\nname@examplewebsite.com\n\n{{ $json.data }}"
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "6a5b523b-58d7-429b-b75a-1857ced2d351",
      "name": "Save Cleaned Lead Data into Airtable",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1856,
        16
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "app7nzaQOYoq0cF1n",
          "cachedResultUrl": "https://airtable.com/app7nzaQOYoq0cF1n",
          "cachedResultName": "GOOGLE MAP SCRAPPING"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblJIrBU19hfFYdL9",
          "cachedResultUrl": "https://airtable.com/app7nzaQOYoq0cF1n/tblJIrBU19hfFYdL9",
          "cachedResultName": "Data"
        },
        "columns": {
          "value": {
            "URL": "={{ $('Clean Google Maps Data').item.json.URL }}",
            "Email": "={{ $json.message.content }}",
            "Title": "={{ $('Clean Google Maps Data').item.json.Title }}",
            "Street": "={{ $('Clean Google Maps Data').item.json.Address }}",
            "Website": "={{ $('Clean Google Maps Data').item.json.Website }}",
            "Phone Number": "={{ $('Clean Google Maps Data').item.json.Phone }}"
          },
          "schema": [
            {
              "id": "Title",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Street",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Street",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Website",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Website",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Phone Number",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Phone Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Email",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "URL",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "5242f67b-ba4d-448f-a22b-24353fb3076b",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -64,
        -320
      ],
      "parameters": {
        "height": 496,
        "content": "### Who is it for?  \nThis workflow is perfect for **marketers, sales teams, agencies, and local businesses** who want to save time by automating **lead generation from Google Maps**.  \n\nIt\u2019s ideal for **real estate agencies, restaurants, service providers, and any local niche** that needs a clean database of fresh contacts, including emails, websites, and phone numbers.  "
      },
      "typeVersion": 1
    },
    {
      "id": "050f1c72-62dc-44e9-90d1-c74088319dac",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        176,
        -928
      ],
      "parameters": {
        "color": 5,
        "height": 1104,
        "content": "## Step 1 \u2013 Scraping Google Maps with Apify\n\nStart simply:\n\n- Open your n8n workflow and choose the trigger: \u201cExecute Workflow\u201d (manual trigger).  \n- Add an **HTTP Request node** (POST method).  \n- Go to [Apify Google Maps Extractor](https://apify.com/compass/google-maps-extractor).  \n- Fill in the fields:  \n\n  - Keyword: e.g., \"real estate agency\" (or restaurant, plumber...)  \n  - Location: \"Paris, France\"  \n  - Number of results: 50 (or more)  \n  - Optional: filters (with/without a website, by categories\u2026)  \n\n- Click **Run** to test the scraper.  \n- Then go to **API \u2192 Endpoints \u2192 Run Actor synchronously and get dataset items**.  \n- Copy the URL and paste it into your HTTP Request node (URL field).  \n- Enable:  \n\n  - Body Content Type \u2192 JSON  \n  - Specify Body Using JSON  \n\n- Go back to Apify, click the JSON tab, copy everything, and paste it into the JSON field of your HTTP Request.  \n"
      },
      "typeVersion": 1
    },
    {
      "id": "ebee292e-a095-4f1e-8371-2ba7ab1eebb6",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        416,
        -416
      ],
      "parameters": {
        "color": 2,
        "width": 272,
        "height": 592,
        "content": "## Step 2 \u2013 Cleaning Things Up (Edit Fields)\n\nRaw data is cool, but messy.  \n\n- Add an **Edit Fields node** (Manual Mapping mode).  \n- Keep only these fields:  \n\nTitle \u2192 {{ $json.title }}\nAddress \u2192 {{ $json.address }}\nWebsite \u2192 {{ $json.website }}\nPhone \u2192 {{ $json.phone }}\nURL \u2192 {{ $json.url }}"
      },
      "typeVersion": 1
    },
    {
      "id": "e5108547-ac63-4148-a1b8-206970200676",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        688,
        -400
      ],
      "parameters": {
        "color": 3,
        "height": 576,
        "content": "## Step 3 \u2013 Handling Each Contact Individually (Loop Over Items)\n\nNext, we process each contact one by one.  \n\n- Add the **Loop Over Items node**  \n- Set **Batch Size** to 20 (or more, depending on your needs).  \n\nThis step avoids traffic jams in the workflow."
      },
      "typeVersion": 1
    },
    {
      "id": "447dec14-b73f-4800-bef7-1d791b76ef42",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        928,
        -256
      ],
      "parameters": {
        "color": 6,
        "width": 256,
        "height": 432,
        "content": "## Step 4 \u2013 Isolating Websites (Edit Fields again)\n\n- Add another **Edit Fields node** (Manual Mapping).  \n- Keep only:  \n\nWebsite \u2192 {{ $json.website }}"
      },
      "typeVersion": 1
    },
    {
      "id": "f7f95c1a-7fb5-4062-a2f1-1786e33ba02a",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1184,
        -320
      ],
      "parameters": {
        "color": 7,
        "width": 256,
        "height": 496,
        "content": "## Step 5 \u2013 Scraping Each Website (HTTP Request)\n\n- Add another **HTTP Request node**  \n- Configure it as:  \n\nMethod: GET\nURL: {{ $json.website }}\n\nThis returns the raw HTML content of each website."
      },
      "typeVersion": 1
    },
    {
      "id": "df761d36-58b3-4d6c-bb15-b35aef012a9f",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1440,
        -608
      ],
      "parameters": {
        "width": 288,
        "height": 784,
        "content": "## Step 6 \u2013 Extracting Emails with ChatGPT\n\n- Add an **OpenAI (Message a Model) node**  \n- Configure as follows:  \n- Model: GPT-4-1-mini (or higher)  \n- Operation: Message a Model  \n- Simplify Output: ON  \n\n## PROMPT GPT\n\nLook at this website content and extract only the email I can contact this business.\nIn your output, provide only the email and nothing else.\nIdeally, this email should be of the business owner, so if you have 2 or more options,\ntry for the most authoritative one. If you don't find any email, output 'Null'.\n\nExemplary output of yours:\nname@examplewebsite.com\n\n{{ $json.data }}"
      },
      "typeVersion": 1
    },
    {
      "id": "283932b7-9d71-4648-8ed9-a511bb5fbda2",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1728,
        -560
      ],
      "parameters": {
        "color": 4,
        "width": 320,
        "height": 736,
        "content": "## Step 7 \u2013 Neatly Store Everything in Airtable\n\nAlmost done!  \n\n- Add an **Airtable \u2192 Create Record node**  \n- Map the fields like this:  \n\n| **Airtable Field** | **Content**                     | **n8n Variable**                           |\n| ------------------ | ------------------------------- | ------------------------------------------ |\n| Title              | Business name                   | `{{ $('Edit Fields').item.json.Title }}`   |\n| Street             | Full address                    | `{{ $('Edit Fields').item.json.Address }}` |\n| Website            | Website URL                     | `{{ $('Edit Fields').item.json.Website }}` |\n| Phone Number       | Phone number                    | `{{ $('Edit Fields').item.json.Phone }}`   |\n| Email              | Email retrieved by AI           | `{{ $json.message.content }}`              |\n| URL                | Google Maps link                | `{{ $('Edit Fields').item.json.URL }}`     |\n\nNow, you have a tidy Airtable database filled with fresh leads.  "
      },
      "typeVersion": 1
    },
    {
      "id": "7afd389c-858e-4d26-8709-26efeafc14a0",
      "name": "Send a message",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2192,
        16
      ],
      "parameters": {
        "sendTo": "={{ $json.fields.Email }}",
        "message": "=Company name: {{ $json.fields.Title }} Website URL: {{ $json.fields.Website }} Phone number: {{ $json.fields[\"Phone Number\"] }} Google Maps: {{ $json.fields.URL }}  vbnet email is now **fully personalized** and based on clear, structured information.",
        "options": {},
        "subject": "={{ $json.fields.Title }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "c1832863-372b-4862-b040-2870c4aa139f",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2048,
        -432
      ],
      "parameters": {
        "color": 6,
        "width": 384,
        "height": 608,
        "content": "## Step 8 \u2013 Automated Email via Gmail (The Final Touch)\n\n- Add a **Gmail \u2192 Send Email node** after your Airtable node.  \n- Configure it like this:  \n\nRecipient (To): {{ $json.fields.Email }}\nSubject: {{ $json.fields.Title }}\n\n**Body Example:**  \n\nCompany name: {{ $json.fields.Title }}\nWebsite URL: {{ $json.fields.Website }}\nPhone number: {{ $json.fields[\"Phone Number\"] }}\nGoogle Maps: {{ $json.fields.URL }}\n\nEach email is now **fully personalized** and based on clear, structured information.  \n"
      },
      "typeVersion": 1
    },
    {
      "id": "d2f3dd40-d553-45f9-8afc-e8602936e354",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        800,
        -928
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 352,
        "content": "![gif](https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExb296OG5sbzJnYjVtbXZndDM3ejkzMXQ1Z3QwM21oa2hsd2ZmZnkwNyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/L5aC2b3jRvURi/giphy.gif)\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "75379dd2-fa4b-4c23-8427-a279f64e287d",
  "connections": {
    "Clean Google Maps Data": {
      "main": [
        [
          {
            "node": "Iterate Through Each Business Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Only Website URLs": {
      "main": [
        [
          {
            "node": "Fetch Raw HTML Content from Business Website",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Workflow Execution": {
      "main": [
        [
          {
            "node": "Fetch Business Data from Google Maps (Apify)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Cleaned Lead Data into Airtable": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Iterate Through Each Business Contact": {
      "main": [
        [],
        [
          {
            "node": "Extract Only Website URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Business Data from Google Maps (Apify)": {
      "main": [
        [
          {
            "node": "Clean Google Maps Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Raw HTML Content from Business Website": {
      "main": [
        [
          {
            "node": "Extract Business Email from Website HTML (GPT-4)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Business Email from Website HTML (GPT-4)": {
      "main": [
        [
          {
            "node": "Save Cleaned Lead Data into Airtable",
            "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.

Pro

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

About this workflow

This workflow is perfect for marketers, sales teams, agencies, and local businesses who want to save time by automating lead generation from Google Maps.

Source: https://n8n.io/workflows/5811/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

This workflow automates the "speed-to-lead" process for insurance agencies. It instantly triggers an AI voice call when a new lead comes in, qualifies their needs via conversation, and automatically g

Form Trigger, Airtable, HTTP Request +3
AI & RAG

This template is perfect for digital agencies, SDRs, lead generators, or outreach teams that want to automatically convert LinkedIn company profiles into high-quality cold emails. If you spend too muc

Form Trigger, HTTP Request, Dropcontact +3
AI & RAG

This intelligent email automation workflow helps you maximize engagement through domain-based outreach. It utilizes AI-powered personalization and strategic follow-ups to increase response rates. The

Gmail, HTTP Request, Google Sheets +1
AI & RAG

This workflow contains community nodes that are only compatible with the self-hosted version of n8n.

Airtable, OpenAI, Form Trigger +3
AI & RAG

Voice Note -> Veo 3 AD. Uses telegramTrigger, telegram, openAi, httpRequest. Event-driven trigger; 49 nodes.

Telegram Trigger, Telegram, OpenAI +3