Google Sheets + SendGrid: local leads, ready to email
Building local lead lists sounds simple until you’re stuck copying Google results, chasing websites, and still ending up with info@ and “no-reply” emails you can’t use. Then you send a few messages and realize half your list is duplicated, outdated, or already contacted.
This is where Sheets SendGrid automation pays off. Agency owners trying to keep client pipelines full feel it fast. A sales lead running outbound, or a solo operator doing local services, runs into the same grind. The outcome is straightforward: fresh prospects in Google Sheets, real emails found automatically, then outreach sent without babysitting.
This workflow turns Google Places discovery into a daily, repeatable system. You’ll see how it finds businesses, filters for quality, extracts emails, sends messages via SendGrid, and keeps your Google Sheet clean.
How This Automation Works
The full n8n workflow, from trigger to final output:
n8n Workflow Template: Google Sheets + SendGrid: local leads, ready to email
flowchart LR
subgraph sg0["Daily Flow"]
direction LR
n1@{ icon: "mdi:play-circle", form: "rounded", label: "Daily Trigger", pos: "b", h: 48 }
n2@{ icon: "mdi:database", form: "rounded", label: "Read Grid Points", pos: "b", h: 48 }
n3["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Filter & Randomize Daily Batch"]
n4["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/httprequest.dark.svg' width='40' height='40' /></div><br/>Google Nearby Search"]
n5["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Parse Nearby Results"]
n6["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Prepare Grid Update"]
n7@{ icon: "mdi:database", form: "rounded", label: "Mark Grid Points as Searched", pos: "b", h: 48 }
n8["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/httprequest.dark.svg' width='40' height='40' /></div><br/>Get Place Details2"]
n9["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Extract Contact Info2"]
n10@{ icon: "mdi:swap-vertical", form: "rounded", label: "Split Places Array2", pos: "b", h: 48 }
n11@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Filter: Has Website2", pos: "b", h: 48 }
n12@{ icon: "mdi:cog", form: "rounded", label: "Wait (Rate Limit)2", pos: "b", h: 48 }
n13["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/httprequest.dark.svg' width='40' height='40' /></div><br/>Scrape Website2"]
n14["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Extract Emails2"]
n15@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Filter: Has Email2", pos: "b", h: 48 }
n16["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Remove Duplicates: Emails"]
n17["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Code in JavaScript"]
n18@{ icon: "mdi:database", form: "rounded", label: "Read Grid Points1", pos: "b", h: 48 }
n19@{ icon: "mdi:database", form: "rounded", label: "Update row in sheet1", pos: "b", h: 48 }
n20@{ icon: "mdi:database", form: "rounded", label: "Append Leads (Dedupe by Plac..", pos: "b", h: 48 }
n21@{ icon: "mdi:swap-vertical", form: "rounded", label: "Loop Over Items", pos: "b", h: 48 }
n1 --> n2
n14 --> n15
n21 --> n14
n21 --> n12
n13 --> n21
n2 --> n17
n18 --> n3
n17 --> n18
n17 --> n19
n15 --> n20
n8 --> n9
n12 --> n13
n6 --> n7
n10 --> n11
n11 --> n16
n4 --> n5
n5 --> n8
n9 --> n10
n16 --> n21
n3 --> n4
n20 --> n6
end
subgraph sg1["Schedule Flow"]
direction LR
n22@{ icon: "mdi:database", form: "rounded", label: "Get row(s) in sheet", pos: "b", h: 48 }
n23@{ icon: "mdi:play-circle", form: "rounded", label: "Schedule Trigger", pos: "b", h: 48 }
n24@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Filter: Remove Fake Emails", pos: "b", h: 48 }
n25["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Assign Alternating Templates"]
n26@{ icon: "mdi:swap-vertical", form: "rounded", label: "Loop Over Items1", pos: "b", h: 48 }
n27@{ icon: "mdi:cog", form: "rounded", label: "Wait", pos: "b", h: 48 }
n28["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/sendGrid.svg' width='40' height='40' /></div><br/>Send an email"]
n29@{ icon: "mdi:database", form: "rounded", label: "Update row in sheet", pos: "b", h: 48 }
n27 --> n29
n28 --> n26
n26 --> n27
n23 --> n22
n22 --> n24
n29 --> n28
n24 --> n25
n25 --> n26
end
subgraph sg2["Flow 3"]
direction LR
n30["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>Webhook"]
n31@{ icon: "mdi:database", form: "rounded", label: "Get row(s) in sheet1", pos: "b", h: 48 }
n32["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>Respond to Webhook"]
n33@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Filter: Remove Fake Emails1", pos: "b", h: 48 }
n30 --> n31
n31 --> n33
n33 --> n32
end
subgraph sg3["Flow 4"]
direction LR
n0@{ icon: "mdi:database", form: "rounded", label: "Save Grid to Sheet (ONE-TIME)", pos: "b", h: 48 }
n34["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Generate Location Grid (ONE-.."]
n34 --> n0
end
%% Styling
classDef trigger fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
classDef ai fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
classDef aiModel fill:#e8eaf6,stroke:#3f51b5,stroke-width:2px
classDef decision fill:#fff8e1,stroke:#f9a825,stroke-width:2px
classDef database fill:#fce4ec,stroke:#c2185b,stroke-width:2px
classDef api fill:#fff3e0,stroke:#e65100,stroke-width:2px
classDef code fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
classDef disabled stroke-dasharray: 5 5,opacity: 0.5
class n1,n23 trigger
class n11,n15,n24,n33 decision
class n2,n7,n18,n19,n20,n22,n29,n31,n0 database
class n4,n8,n13,n30,n32 api
class n3,n5,n6,n9,n14,n16,n17,n25,n34 code
classDef customIcon fill:none,stroke:none
class n3,n4,n5,n6,n8,n9,n13,n14,n16,n17,n25,n28,n30,n32,n34 customIcon
The Problem: Local lead gen breaks the moment you scale it
Manually pulling prospects from Google is fine for a day. Then you try to do it every day. You search a city, open 20 tabs, copy names and addresses, click through to websites, and still miss phone numbers or the right contact email. Worse, you repeat searches without realizing it, so your list fills with duplicates and “already contacted” businesses. The mental load is real, and the opportunity cost is brutal: time you could spend pitching, following up, or improving your offer gets burned on list-building busywork.
It adds up fast. Here’s where it usually breaks down.
- You lose about 2 hours just gathering a small batch of leads, and it still needs cleanup before it’s usable.
- Generic emails and placeholder addresses sneak in, so your deliverability takes a hit when you start sending.
- You re-check the same neighborhoods and categories because there’s no reliable “already searched” record.
- Sending outreach becomes a separate project, which means leads go stale while you “get to it later.”
The Solution: A daily Google Places → Sheets → SendGrid pipeline
This n8n workflow automates the entire loop: it searches Google Places on a schedule, stores businesses in Google Sheets, visits their websites to extract email addresses, filters out junk, and sends personalized cold emails through SendGrid. It starts with a location grid (lat/long points) that covers target areas, then selects a daily batch of points so you don’t hammer your API quota. For each point, it pulls nearby businesses, fetches richer details (like phone and website), and ignores listings that don’t have a website at all. After that, it scrapes the website content, finds email addresses, removes placeholders, deduplicates by domain, and records everything in your leads database sheet. Finally, it rotates between two outreach templates, rate-limits sending with built-in waits, and marks leads as sent so you don’t double-message people.
The workflow starts on a daily schedule and grabs a set of grid coordinates. Those coordinates drive Google Places searches and enrichment, then website scraping. Once emails are confirmed, SendGrid sends outreach with a short delay between sends, and Google Sheets is updated as the source of truth.
What You Get: Automation vs. Results
| What This Workflow Automates | Results You’ll Get |
|---|---|
|
|
Example: What This Looks Like
Say you want steady outbound for local service businesses. Manually, doing 50 Google searches, opening websites, and hunting for emails can easily take about 3 hours, and that’s before sending a single message. With this workflow, the daily schedule runs the searches, enrichment, and scraping automatically. You mostly just glance at the Google Sheet and replies, while SendGrid handles outreach with 30-second gaps between sends. The “work” becomes review, not grunt labor.
What You’ll Need
- n8n instance (try n8n Cloud free)
- Self-hosting option if you prefer (Hostinger works well)
- Google Sheets to store your grid and lead database.
- Google Places API for Nearby Search and Details data.
- SendGrid to send outreach and track delivery.
- Google Cloud API key (create it in Google Cloud Console).
Skill level: Intermediate. You’ll connect accounts, add API keys, and adjust a few filters and templates.
Don’t want to set this up yourself? Talk to an automation expert (free 15-minute consultation).
How It Works
Daily scheduled run picks your search areas. n8n pulls grid coordinates from Google Sheets, shuffles them, and selects a daily batch (50 points by default) so coverage grows without repeating the same spots.
Google Places finds businesses, then enriches each listing. The workflow calls the Nearby API, parses results, and then hits the Details API to pull extra fields like phone, hours, and most importantly, the website.
Web scraping extracts usable contact emails. For each business with a website, it waits briefly (rate limit), fetches page content, extracts emails, removes obvious junk, and deduplicates by domain so you don’t stack duplicates.
SendGrid sends and Google Sheets tracks what happened. A template is selected (alternating for variety), a short delay is applied between sends, and the lead record is marked so you have clean “sent vs not sent” visibility.
You can easily modify the target cities to match your service area based on your needs. See the full implementation guide below for customization options.
Step-by-Step Implementation Guide
Step 1: Configure the Schedule Triggers
Set up the scheduled and webhook entry points that start the daily grid scan and outreach workflows.
- Open Daily Schedule Trigger and set your daily run time for grid scanning.
- Open Scheduled Start Trigger and set the schedule for outbound outreach processing.
- Open Incoming Webhook Entry and copy the Webhook URL for external calls that fetch lead rows on demand.
- Confirm the execution order: Daily Schedule Trigger → Retrieve Grid Coordinates and Scheduled Start Trigger → Fetch Sheet Rows.
Step 2: Connect Google Sheets
This workflow relies heavily on Google Sheets for grids, leads, and status updates. Connect credentials once and apply them across all Sheets nodes.
- Open any Google Sheets node, such as Store Grid in Sheet Once or Retrieve Grid Coordinates.
- Credential Required: Connect your Google Sheets credentials.
- Apply the same credentials to all Google Sheets nodes: Store Grid in Sheet Once, Retrieve Grid Coordinates, Retrieve Grid Coordinates B, Update Sheet Row B, Append Leads by Place ID, Flag Grid Points Searched, Fetch Sheet Rows, Update Sheet Row, and Fetch Sheet Rows B.
⚠️ Common Pitfall: If any Google Sheets node runs without credentials, the workflow will fail silently. Double-check that every Sheets node has credentials selected.
Step 3: Set Up Processing Nodes
Configure the data processing and batching logic that drives the grid scan, place collection, and email extraction pipeline.
- Review Generate Location Grid Once and Custom JS Processing to ensure grid logic matches your target area.
- Confirm that Custom JS Processing outputs to both Retrieve Grid Coordinates B and Update Sheet Row B in parallel.
- Validate the daily selection logic in Filter & Shuffle Daily Set before it feeds Nearby Places API Call.
- Ensure the parsing chain is intact: Nearby Places API Call → Parse Nearby Places Data → Fetch Place Details → Extract Contact Details → Split Places Collection.
- Verify filtering and batching: Filter Records with Website → Remove Duplicate Emails → Iterate Records Batch → Extract Email Addresses → Filter Records with Email.
- Keep rate limiting intact by preserving Delay for Rate Limit before Scrape Website Content.
Tip: Scrape Website Content has continueOnFail enabled, which helps prevent batch failures when a site blocks scraping.
Step 4: Configure Output and Action Nodes
Finalize lead storage, grid flag updates, and email dispatch.
- Confirm lead storage flow: Filter Records with Email → Append Leads by Place ID → Prepare Grid Update Payload → Flag Grid Points Searched.
- Verify outbound email flow: Filter Out Fake Emails → Set Alternating Templates → Batch Iterate Templates → Pause Before Send → Update Sheet Row → Dispatch Email Notice.
- Credential Required: Connect your SendGrid credentials in Dispatch Email Notice.
- Ensure webhook response flow is wired: Incoming Webhook Entry → Fetch Sheet Rows B → Filter Out Fake Emails B → Return Webhook Response.
⚠️ Common Pitfall: If Dispatch Email Notice lacks SendGrid credentials, emails will not send even though the batch completes.
Step 5: Test and Activate Your Workflow
Run controlled tests to validate the daily grid scan and outreach messaging before turning the workflow on.
- Click Execute Workflow and manually run Daily Schedule Trigger to validate the grid scan path through Nearby Places API Call.
- Trigger Incoming Webhook Entry using the test URL and confirm a response from Return Webhook Response.
- Check Google Sheets to confirm new lead rows were created by Append Leads by Place ID and grid flags were set by Flag Grid Points Searched.
- Verify that emails were sent and logged by Dispatch Email Notice and updated by Update Sheet Row.
- When successful, toggle the workflow to Active to enable scheduled runs.
Common Gotchas
- Google Places API credentials can expire or hit restricted scopes. If things break, check your Google Cloud Console API key restrictions and quota dashboard first.
- If you’re using Wait nodes or external scraping, processing times vary. Bump up the wait duration if downstream nodes fail on empty responses.
- SendGrid permissions matter, and unverified sender identities can block sending. Confirm your verified sender/domain in SendGrid before blaming the workflow.
Frequently Asked Questions
About 45 minutes if you already have your API keys and Sheets ready.
No. You’ll mostly connect accounts, paste an API key, and edit the email templates.
Yes. n8n has a free self-hosted option and a free trial on n8n Cloud. Cloud plans start at $20/month for higher volume. You’ll also need to factor in Google Places API usage and your SendGrid email plan.
Two options: n8n Cloud (managed, easiest setup) or self-hosting on a VPS. For self-hosting, Hostinger VPS is affordable and handles n8n well. Self-hosting gives you unlimited executions but requires basic server management.
Yes, and it’s the point of doing it in n8n. You can change the location grid inputs (the “Generate Location Grid” logic and the grid Google Sheet) to cover new cities, then adjust the Google Places search query and filters to focus on certain categories. Many teams also tweak the “Filter Records with Website” rule to be stricter, and update the two alternating templates to match their offer. If you want to get fancy, add an extra filter to exclude low-rated listings or businesses marked as temporarily closed.
Usually it’s an expired OAuth token or the wrong spreadsheet permissions. Reconnect Google Sheets in n8n and make sure the account has edit access to both the grid sheet and the leads database sheet. Also check that the sheet tabs still have the same names, because renamed tabs can break lookups.
On the default setup, it processes about 50 search points per day, and the main constraint is your Google Places quota and how aggressively you scrape websites. If you self-host n8n, there’s no execution cap from n8n itself, but your server and API limits will still matter. On n8n Cloud, higher plans support larger monthly volumes. Practically, most teams scale by increasing daily batch size slowly and watching deliverability.
Often, yes, because this workflow isn’t just “A to B.” You’ve got batching, waits for rate limiting, branching logic, scraping, deduplication, and a webhook dashboard endpoint, all in one place. Zapier and Make can do parts of that, but the moment you add loops and complex filters, the setup gets messy and costs climb. n8n is also flexible if you want to self-host and run high volume without paying per tiny step. If you only need a simple 2-step “new row → send email” automation, Zapier is quick. Talk to an automation expert if you’re not sure which fits.
Once this is running, your “lead gen” becomes a quiet daily habit in the background. The workflow handles the repetitive stuff, and you focus on offers, follow-ups, and closing.
Need Help Setting This Up?
Our automation experts can build and customize this workflow for your specific needs. Free 15-minute consultation—no commitment required.