Google Sheets to WhatsApp, personalized outreach at scale
Your lead list is in Google Sheets. Your outreach happens in WhatsApp. And somehow you’re still copying names, double-checking numbers, researching each company, then trying to remember who got what message.
This is where Sales Ops starts drowning, but agency owners and growth-minded consultants feel it too. Sheets WhatsApp outreach should not take half your morning. This workflow turns a messy “list + manual effort” routine into a repeatable system that can handle real volume.
You’ll see how the automation pulls leads, enriches them with research, writes personalized messages using AI, and sends (and tracks) WhatsApp outreach without losing control of follow-ups.
How This Automation Works
The full n8n workflow, from trigger to final output:
n8n Workflow Template: Google Sheets to WhatsApp, personalized outreach at scale
flowchart LR
subgraph sg0["When chat message received Flow"]
direction LR
n32@{ icon: "mdi:robot", form: "rounded", label: "AI Agent1", pos: "b", h: 48 }
n33@{ icon: "mdi:vector-polygon", form: "rounded", label: "Embeddings OpenAI3", pos: "b", h: 48 }
n34@{ icon: "mdi:cube-outline", form: "rounded", label: "RAG1", pos: "b", h: 48 }
n35@{ icon: "mdi:cube-outline", form: "rounded", label: "Leads1", pos: "b", h: 48 }
n36@{ icon: "mdi:vector-polygon", form: "rounded", label: "Embeddings OpenAI4", pos: "b", h: 48 }
n37@{ icon: "mdi:play-circle", form: "rounded", label: "When chat message received", pos: "b", h: 48 }
n38@{ icon: "mdi:memory", form: "rounded", label: "Chat Memory", pos: "b", h: 48 }
n39@{ icon: "mdi:brain", form: "rounded", label: "Chat Model", pos: "b", h: 48 }
n40@{ icon: "mdi:robot", form: "rounded", label: "Reranker Cohere", pos: "b", h: 48 }
n41@{ icon: "mdi:robot", form: "rounded", label: "Reranker Cohere1", pos: "b", h: 48 }
n34 -.-> n32
n35 -.-> n32
n39 -.-> n32
n38 -.-> n32
n40 --> n34
n41 --> n35
n33 -.-> n34
n36 -.-> n35
n37 --> n32
end
subgraph sg1["WAHA Flow"]
direction LR
n7@{ icon: "mdi:robot", form: "rounded", label: "AI Agent", pos: "b", h: 48 }
n8@{ icon: "mdi:brain", form: "rounded", label: "OpenAI Chat Model", pos: "b", h: 48 }
n9@{ icon: "mdi:memory", form: "rounded", label: "Postgres Chat Memory", pos: "b", h: 48 }
n10@{ icon: "mdi:vector-polygon", form: "rounded", label: "Embeddings OpenAI1", pos: "b", h: 48 }
n28@{ icon: "mdi:cube-outline", form: "rounded", label: "RAG", pos: "b", h: 48 }
n29@{ icon: "mdi:cube-outline", form: "rounded", label: "Leads", pos: "b", h: 48 }
n30@{ icon: "mdi:vector-polygon", form: "rounded", label: "Embeddings OpenAI2", pos: "b", h: 48 }
n31@{ icon: "mdi:play-circle", form: "rounded", label: "WAHA Trigger", pos: "b", h: 48 }
n28 -.-> n7
n29 -.-> n7
n31 --> n7
n8 -.-> n7
n10 -.-> n28
n30 -.-> n29
n9 -.-> n7
end
subgraph sg2["Google Drive Flow"]
direction LR
n0@{ icon: "mdi:cog", form: "rounded", label: "Search File", pos: "b", h: 48 }
n1@{ icon: "mdi:cog", form: "rounded", label: "Get Data", pos: "b", h: 48 }
n2@{ icon: "mdi:cube-outline", form: "rounded", label: "Supabase Vector Store", pos: "b", h: 48 }
n3@{ icon: "mdi:vector-polygon", form: "rounded", label: "Embeddings OpenAI", pos: "b", h: 48 }
n4@{ icon: "mdi:robot", form: "rounded", label: "Default Data Loader", pos: "b", h: 48 }
n5@{ icon: "mdi:robot", form: "rounded", label: "Recursive Character Text Spl..", pos: "b", h: 48 }
n6@{ icon: "mdi:swap-vertical", form: "rounded", label: "Loop Over Items", pos: "b", h: 48 }
n11@{ icon: "mdi:play-circle", form: "rounded", label: "Google Drive Trigger", pos: "b", h: 48 }
n1 --> n6
n0 --> n1
n6 --> n2
n3 -.-> n2
n4 -.-> n2
n11 --> n0
n2 --> n6
n5 -.-> n4
end
subgraph sg3["Google Sheets Flow"]
direction LR
n21@{ icon: "mdi:cube-outline", form: "rounded", label: "Supabase Vector Store2", pos: "b", h: 48 }
n22@{ icon: "mdi:vector-polygon", form: "rounded", label: "small3", pos: "b", h: 48 }
n23@{ icon: "mdi:robot", form: "rounded", label: "Default Data Loader2", pos: "b", h: 48 }
n24@{ icon: "mdi:robot", form: "rounded", label: "Recursive Character Text Spl..", pos: "b", h: 48 }
n25@{ icon: "mdi:play-circle", form: "rounded", label: "Google Sheets Trigger", pos: "b", h: 48 }
n26["<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/>Transform for Vector"]
n27["<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/>Check Existing Data"]
n22 -.-> n21
n27 --> n21
n23 -.-> n21
n26 --> n27
n25 --> n26
n24 -.-> n23
end
subgraph sg4["When clicking ‘Execute workflow’ Flow"]
direction LR
n12@{ icon: "mdi:play-circle", form: "rounded", label: "When clicking ‘Execute workf..", 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 Maps"]
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/httprequest.dark.svg' width='40' height='40' /></div><br/>HTTP Request1"]
n18["<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 Result"]
n19@{ icon: "mdi:database", form: "rounded", label: "Append Leads", pos: "b", h: 48 }
n42@{ icon: "mdi:swap-vertical", form: "rounded", label: "Set Location", pos: "b", h: 48 }
n18 --> n19
n13 --> n18
n19 --> n17
n42 --> n13
n12 --> n42
end
subgraph sg5["Flow 6"]
direction LR
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/>Clean Data"]
n15@{ icon: "mdi:database", form: "rounded", label: "Update Data", 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/webhook.dark.svg' width='40' height='40' /></div><br/>Webhook"]
n20@{ icon: "mdi:database", form: "rounded", label: "Get Leads", pos: "b", h: 48 }
n16 --> n20
n20 --> n14
n14 --> n15
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 n37,n31,n11,n25,n12 trigger
class n32,n40,n41,n7,n4,n5,n23,n24 ai
class n39,n8 aiModel
class n38,n9 ai
class n34,n35,n28,n29,n2,n21 ai
class n33,n36,n10,n30,n3,n22 ai
class n19,n15,n20 database
class n13,n17,n18,n16 api
class n26,n27,n14 code
class n31 disabled
class n17 disabled
class n16 disabled
classDef customIcon fill:none,stroke:none
class n26,n27,n13,n17,n18,n14,n16 customIcon
The Problem: Personalization Doesn’t Scale in a Spreadsheet
Most outreach starts with good intentions: build a clean list, do a little research, send a thoughtful message. Then reality hits. You need to scrape business info, find a relevant angle, write something that doesn’t sound templated, and actually send it at the right time. Doing that across 50 or 200 leads turns into a week of context switching. Worse, the spreadsheet becomes “kind of accurate” at best, so you end up resending messages or forgetting follow-ups. It’s exhausting, honestly.
The friction compounds once you try to scale.
- Research lives in tabs, browser history, and random notes, so your messages drift toward generic.
- Someone updates a row, but the outreach status doesn’t follow, which means you lose track of what was sent.
- Manual WhatsApp sending slows you down to a crawl and increases mistakes with names, links, and phone formatting.
- When replies come in, there’s no consistent way to route them, log them, and decide the next step.
The Solution: Google Sheets → Research → WhatsApp, Automatically
This workflow takes your Google Sheets lead list and turns it into a structured outreach pipeline. A sheet update (or a webhook call) kicks things off, then the workflow cleans and validates each lead record so phone numbers, company fields, and locations are consistent. Next, it enriches leads through scraping (via HTTP requests) and uses an AI agent with RAG (retrieval-augmented generation) to write messages that reference real details about the prospect. Finally, the workflow pushes personalized WhatsApp messages through a WAHA integration, while updating your sheet rows so send status and next actions stay visible.
It starts with a trigger from Google Sheets or a webhook. Then research and message generation happen in the background using OpenAI plus your own company docs stored in a vector index (Supabase). Once a message is ready, WhatsApp delivery and ongoing reply handling are automated, so your team can focus on real conversations.
What You Get: Automation vs. Results
| What This Workflow Automates | Results You’ll Get |
|---|---|
|
|
Example: What This Looks Like
Say you message 60 leads a week. Manually, you might spend about 5 minutes researching each lead, then another 2 minutes crafting and sending a WhatsApp message, plus a minute updating the sheet. That’s roughly 8 minutes per lead, or about 8 hours weekly. With this workflow, you update or add rows once (maybe 15 minutes total), let scraping and AI run in the background, then review and monitor replies. For many teams that turns “a day of busywork” into “a short daily check-in.”
What You’ll Need
- n8n instance (try n8n Cloud free)
- Self-hosting option if you prefer (Hostinger works well)
- Google Sheets to store leads and outreach status
- Google Drive for your company docs and sales assets
- OpenAI API key (get it from the OpenAI API dashboard)
Skill level: Intermediate. You’ll connect accounts, add API keys, and adjust a few message fields and triggers.
Don’t want to set this up yourself? Talk to an automation expert (free 15-minute consultation).
How It Works
A lead change triggers the workflow. A Google Sheets update trigger, a manual launch, or an incoming webhook starts the run, depending on how you want to operate (scheduled batches, manual pushes, or API-driven).
Lead data gets cleaned and prepared. Code steps sanitize records, normalize fields like location, and update the sheet rows so your pipeline doesn’t slowly rot from inconsistent formatting.
Research and personalization happen automatically. HTTP requests fetch scraping results, then documents from Google Drive are chunked and embedded into Supabase vector indexes. An AI agent uses that context to generate a message that sounds like you actually looked at the prospect.
WhatsApp outreach and reply handling kick in. Messages go out through the WAHA WhatsApp integration, and inbound WhatsApp events can trigger “insight” and “chat intelligence” agents to guide follow-ups and log conversation state.
You can easily modify message tone and qualification rules to match your offer and audience. See the full implementation guide below for customization options.
Step-by-Step Implementation Guide
Step 1: Configure the Drive Update Trigger
This workflow starts with file updates from Google Drive, so the first step is to configure the drive trigger that watches a specific folder.
- Select Drive Update Trigger and set Event to
fileUpdated. - Set Trigger On to
specificFolder. - Choose the folder in Folder To Watch with the ID value
[YOUR_ID]. - Credential Required: Connect your
googleDriveOAuth2Apicredentials.
[YOUR_ID] placeholder (folder IDs, document IDs, and API URLs) or the workflow will fail.Step 2: Connect Google Drive file retrieval
The drive trigger hands off to file discovery and download so files can be processed into the vector index.
- Open Locate Drive Files and set Resource to
fileFolderwith Return All enabled. - Set the folder filter Folder ID to
[YOUR_ID]so only the target folder is scanned. - Credential Required: Connect your
googleDriveOAuth2Apicredentials for Locate Drive Files. - In Download Drive Item, keep Operation set to
downloadand set File ID to{{ $json.id }}. - Credential Required: Connect your
googleDriveOAuth2Apicredentials for Download Drive Item.
Step 3: Set up document ingestion to Supabase vectors
Downloaded files are chunked, embedded, and inserted into Supabase using the vector index path.
- Configure Default Document Loader to use Data Type
binary. - Keep Recursive Text Chunker with default settings for chunking.
- Set Supabase Vector Index to Mode
insertand Table Namedocuments. - Credential Required: Connect your
supabaseApicredentials for Supabase Vector Index. - Ensure OpenAI Embedding Gen is connected as the embedding model for Supabase Vector Index.
- Credential Required: Connect your
openAiApicredentials for OpenAI Embedding Gen.
Step 4: Configure lead scraping and Google Sheets output
The manual path allows scraping from Maps and appending lead results to Google Sheets.
- In Manual Launch Trigger, leave default settings so you can test the scraping path manually.
- In Assign Location, set lokasi to
Bali. - In Maps Scraper, set Method to
POSTand set URL to[YOUR_ID]. - In Maps Scraper, set JSON Body to the provided payload and ensure locationQuery uses
{{ $json.lokasi }}. - In Fetch Scrape Result, set URL to
[YOUR_ID]. - In Append Lead Rows, set Operation to
appendand Authentication toserviceAccount. - Set Document ID to
[YOUR_ID]and Sheet Name togid=0in Append Lead Rows. - Credential Required: Connect your
googleApicredentials for Append Lead Rows.
[YOUR_ID] with real API endpoints.Step 5: Configure Sheets cleanup and enrichment
Incoming rows from Sheets are sanitized and updated with enriched fields and lead scoring.
- In Incoming Webhook, set Path to
[YOUR_ID]if you plan to enable this trigger. - Open Retrieve Lead Sheet and select the Google Sheet via Document ID and Sheet Name.
- Credential Required: Connect your
googleApicredentials for Retrieve Lead Sheet. - Review Sanitize Records to confirm it outputs cleaned fields like
lead_score,lead_quality, andbusiness_summary. - In Update Sheet Rows, set Operation to
updateand Authentication toserviceAccount. - Ensure the row_number mapping uses
{{ $('Retrieve Lead Sheet').item.json.row_number }}and confirm column mappings match your sheet headers. - Credential Required: Connect your
googleApicredentials for Update Sheet Rows.
Step 6: Set up Sheets update trigger to Supabase lead index
When the sheet updates, a vector payload is prepared, validated, and inserted into the lead index.
- Configure Sheets Update Trigger to watch Sheet Name
gid=0and Document ID[YOUR_ID]. - Credential Required: Connect your
googleSheetsTriggerOAuth2Apicredentials for Sheets Update Trigger. - In Prepare Vector Payload, keep the existing JS so it outputs
business_summaryandmetadatafields. - In Validate Existing Entries, keep the unique ID logic based on
${data.Title}_${data.Address}. - Set Supabase Lead Index to Mode
insertand Table Namerestaurant_leads. - Credential Required: Connect your
supabaseApicredentials for Supabase Lead Index. - Ensure OpenAI Embedding Gen 3 is attached as the embedding model to Supabase Lead Index.
- Credential Required: Connect your
openAiApicredentials for OpenAI Embedding Gen 3.
Step 7: Configure chat intelligence and memory
The chat layer provides intelligence using company documents and restaurant leads, with memory and reranking.
- Keep Chat Message Trigger enabled to receive incoming chat messages.
- In Chat Intelligence Agent, review the system instructions to enforce the mandatory tool usage.
- Ensure Chat Language Model is connected to Chat Intelligence Agent and uses model
gpt-4o-mini. - Credential Required: Connect your
openAiApicredentials for Chat Language Model. - Enable memory by keeping Chat History Memory with Context Window Length set to
20. - Credential Required: Connect your
postgrescredentials for Chat History Memory.
Step 8: Configure tools, embeddings, and rerankers for AI retrieval
This workflow uses multiple Supabase vector tools and OpenAI embedding nodes. Configure them by function rather than individually.
- For all Supabase vector tools (Company Docs Tool, Leads Tool, Company Docs Rerank Tool, Leads Rerank Tool), set Mode to
retrieve-as-tooland Top K to20. - Credential Required: Connect your
supabaseApicredentials for all vector tool nodes. - Ensure embeddings are connected: OpenAI Embedding Gen 2 to Company Docs Tool, OpenAI Embedding Gen 4 to Leads Tool, OpenAI Embedding Gen 5 to Company Docs Rerank Tool, and OpenAI Embedding Gen 6 to Leads Rerank Tool.
- Credential Required: Connect your
openAiApicredentials for all OpenAI embedding nodes. - Set both Cohere Reranker A and Cohere Reranker B to Model Name
rerank-multilingual-v3.0. - Credential Required: Connect your
cohereApicredentials for Cohere Reranker A and Cohere Reranker B.
Step 9: Configure insight agent and WhatsApp memory (optional path)
An alternate conversational path uses WhatsApp trigger, memory, and an agent configured for business intelligence.
- If you enable WAHA Incoming Trigger, connect it to Insight Agent as shown in the workflow.
- In Insight Agent, keep Text set to
{{ $json.payload._data.key.id }}and Prompt Type set todefine. - Confirm OpenAI Chat Engine is connected as the language model for Insight Agent.
- Credential Required: Connect your
openAiApicredentials for OpenAI Chat Engine. - Set Postgres Conversation Memory Session Key to
{{ $('WAHA Incoming Trigger').item.json.payload._data.key.remoteJid }}and Context Window Length to20. - Credential Required: Connect your
postgrescredentials for Postgres Conversation Memory.
Step 10: Test and Activate Your Workflow
Test each path to confirm data flows correctly, then activate for production use.
- Click Execute Workflow and trigger Manual Launch Trigger to test the scrape flow from Assign Location → Maps Scraper → Append Lead Rows.
- Update a file in the watched Drive folder to verify Drive Update Trigger → Locate Drive Files → Download Drive Item runs and data is inserted into Supabase Vector Index.
- Edit a row in your sheet to confirm Sheets Update Trigger → Prepare Vector Payload → Supabase Lead Index inserts vectors.
- Send a test chat to Chat Message Trigger and verify Chat Intelligence Agent responds with sources from Company Docs Rerank Tool or Leads Rerank Tool.
- When successful, toggle the workflow to Active for production use.
Common Gotchas
- Google Sheets credentials can expire or need specific permissions. If things break, check the n8n Credentials page and the sharing settings on the spreadsheet first.
- If you’re using Wait nodes or external rendering, processing times vary. Bump up the wait duration if downstream nodes fail on empty responses.
- Default prompts in AI nodes are generic. Add your brand voice early or you’ll be editing outputs forever.
Frequently Asked Questions
About 60–90 minutes if your accounts and API keys are ready.
No. You’ll mostly connect credentials and edit a few fields. The included code steps are already built, so you’re not writing scripts from scratch.
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 OpenAI API usage (often a few dollars a month at modest volume) plus any scraping or WhatsApp provider costs.
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, but plan to spend a bit of time here. The fastest win is updating the prompts used by the Insight Agent and Chat Intelligence Agent so the tone matches your offer (more direct for agencies, more consultative for B2B services). You can also change what gets retrieved from your vector index by swapping the company docs sources in Supabase and adjusting the chunking/embedding steps. Common customizations include a “qualification first” message, a pricing-first script, and industry-specific proof points pulled from different Drive folders.
Usually it’s credentials or session state in WAHA. Check the WAHA instance logs and confirm the account is still connected, then re-save the WAHA credentials in n8n. If messages send sometimes but not always, it can also be rate limiting from your WhatsApp setup or invalid phone formatting coming from Google Sheets (missing country code is the classic).
If you self-host, it’s mostly limited by your server and third-party rate limits, so hundreds of leads per day is realistic with batching.
For AI-heavy outreach like this, n8n is usually the better fit because you can run complex logic (batching, branching, retries, memory) without fighting platform limits. Self-hosting also matters when you’re indexing documents, storing conversation memory, and running lots of executions. Zapier or Make can still work for a simpler “new row → send message” flow, and they can feel quicker at first. But once you add scraping, RAG, and reply handling, it gets expensive and brittle. If you’re on the fence, Talk to an automation expert and map your exact volume and channels.
Once this is running, your spreadsheet stops being a to-do list and becomes a system. The workflow handles the repetitive outreach mechanics so you can spend your time where it actually matters: replies.
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.