I send 200+ personalized cold emails per day and spend zero time writing them. The system runs on n8n, costs under $50/month in API calls, and books more meetings than the SDR I used to pay $4k/month.
Here is exactly how it works.
The Problem Nobody Talks About
Manual lead generation is a hiring problem disguised as a sales problem. You either pay someone $40-60k/year to do repetitive research, or you do it yourself and burn hours that should go toward closing deals or building product.
The typical flow looks like this:
- Search Google Maps or LinkedIn for prospects
- Find their email somehow
- Write a "personalized" email (that is really just a template with their name swapped in)
- Send it, forget about it, repeat
This does not scale. Worse, the "personalization" is shallow enough that prospects see right through it. The open rates reflect that — 15-20% if you are lucky.
I wanted a system that could research a business deeply enough to write an email a human would actually read. And I wanted it running on autopilot.
The Architecture
Here is the full pipeline, end to end:
Google Maps Scraping
|
v
Hunter.io Email Enrichment
|
v
AI Lead Scoring (GPT-4)
|
v
Personalized Email Generation
|
v
Automated Sending Sequences
|
v
Reply Detection + CRM Update
|
v
Call Prep Notes (auto-generated)
Every stage is an n8n workflow. Some are triggered on a schedule, some are triggered by webhooks. They pass data between each other using n8n's built-in workflow trigger nodes and a shared Postgres database.
Let me walk through each one.
Stage 1: Lead Scraping from Google Maps
The first workflow runs on a daily cron. It takes a list of search queries (e.g., "marketing agency in Austin TX", "dentist in Phoenix AZ") and hits the Google Maps API via SerpAPI.
{
"node": "SerpAPI - Google Maps",
"type": "httpRequest",
"method": "GET",
"url": "https://serpapi.com/search.json",
"queryParameters": {
"engine": "google_maps",
"q": "={{ $json.searchQuery }}",
"ll": "={{ $json.coordinates }}",
"type": "search",
"api_key": "{{ $credentials.serpApiKey }}"
}
}
Each result gives you the business name, address, phone number, website, rating, and review count. I filter out anything under 4 stars or with fewer than 10 reviews — those businesses typically are not ready to invest in services.
The results get deduplicated against the existing database and inserted into a leads_raw table.
Daily yield: ~150-300 new leads depending on query volume.
Stage 2: Email Enrichment with Hunter.io
A second workflow triggers every time new rows land in leads_raw. It takes the company website domain and runs it through Hunter.io's domain search endpoint.
{
"node": "Hunter.io - Domain Search",
"type": "httpRequest",
"method": "GET",
"url": "https://api.hunter.io/v2/domain-search",
"queryParameters": {
"domain": "={{ $json.website_domain }}",
"api_key": "{{ $credentials.hunterApiKey }}"
}
}
Hunter returns email addresses associated with the domain, along with confidence scores and the person's role. I prioritize in this order:
- Owner / Founder / CEO — decision maker, best conversion
- Marketing Director / CMO — if selling marketing services
- Operations Manager — catch-all for service businesses
Only emails with a confidence score above 80% move forward. The rest get flagged for manual review (which, honestly, I rarely get to — and that is fine).
Stage 3: AI Lead Scoring
This is where it gets interesting. Before writing a single email, I have GPT-4 score each lead on a 1-10 scale based on signals pulled from the scrape.
{
"node": "OpenAI - Lead Scoring",
"type": "openAi",
"operation": "chat",
"model": "gpt-4",
"messages": [
{
"role": "system",
"content": "You are a B2B lead scoring assistant. Score this lead 1-10 based on: review count (social proof), rating (reputation), website quality signals, and industry fit. Return JSON with score and reasoning."
},
{
"role": "user",
"content": "Business: {{ $json.name }}\nIndustry: {{ $json.category }}\nRating: {{ $json.rating }}\nReviews: {{ $json.review_count }}\nWebsite: {{ $json.website }}\nLocation: {{ $json.city }}, {{ $json.state }}"
}
]
}
The scoring prompt also pulls in the business's website via an HTTP request node and passes a trimmed version of the page content to GPT-4. This gives the model context about what services they offer, how sophisticated their current marketing is, and whether they look like they are already working with an agency.
Leads scoring 7+ go into the email sequence. Leads scoring 4-6 go into a nurture list. Below 4, they get archived.
Stage 4: Personalized Email Generation
This is the node that does the heavy lifting. For each qualified lead, GPT-4 writes a fully personalized cold email using everything we know about the business.
{
"node": "OpenAI - Email Writer",
"type": "openAi",
"operation": "chat",
"model": "gpt-4",
"messages": [
{
"role": "system",
"content": "Write a cold outreach email. Be direct, no fluff. Reference something specific about their business. Keep it under 120 words. No generic compliments. End with a clear, low-friction CTA. Do not use the word 'excited' or 'thrilled'. Sound like a human, not a marketer."
},
{
"role": "user",
"content": "Recipient: {{ $json.contact_name }}, {{ $json.role }} at {{ $json.business_name }}\nIndustry: {{ $json.category }}\nCity: {{ $json.city }}\nRating: {{ $json.rating }} ({{ $json.review_count }} reviews)\nWebsite notes: {{ $json.website_summary }}\nOur service: AI automation for small businesses"
}
]
}
Here is what a real output looks like:
Subject: Quick question about ops at Riverside Dental
Hey Dr. Patel,
Noticed Riverside Dental is sitting at 4.8 stars across 340+ reviews in Mesa — that kind of reputation usually means the phone is ringing more than your front desk can handle.
I build automation systems that handle appointment scheduling, follow-ups, and review requests without adding headcount. One of my dental clients cut 12 hours/week of admin time within the first month.
Worth a 15-minute call this week to see if it fits?
— Yossi
That email references their specific rating, review count, city, and a pain point relevant to their industry. It is not a template with merge fields. The entire structure and angle shifts based on the lead data.
Stage 5: Sending Sequences and Follow-ups
Emails get queued in a send_queue table with scheduled send times staggered throughout the day (no one wants 200 emails hitting from the same domain at 9:00 AM sharp).
The sending workflow runs every 15 minutes, picks up the next batch, and sends via SMTP through a warmed domain. If no reply after 3 days, a follow-up triggers automatically — also AI-written, referencing the original email.
{
"node": "IF - Check Reply Status",
"type": "if",
"conditions": {
"conditions": [
{
"value1": "={{ $json.reply_received }}",
"operation": "equal",
"value2": false
},
{
"value1": "={{ $json.followup_count }}",
"operation": "lessThan",
"value2": 3
}
]
}
}
The sequence caps at 3 follow-ups. After that, the lead goes dormant. Respecting inboxes is not just polite — it protects your domain reputation.
Stage 6: Reply Detection and Call Prep
An IMAP trigger monitors the inbox for replies. When one comes in, it matches the sender back to the lead record and does two things:
- Updates the CRM status — marks the lead as "replied" and tags the sentiment (positive, negative, question) using a quick GPT-4 classification.
- Generates call prep notes — if the reply is positive, a separate workflow fires that pulls together everything we know about the business into a one-page brief.
The call prep output includes the lead's Google Maps data, website summary, their email reply, suggested talking points, and potential objections based on their industry. My calendar booking link goes out in the reply, and by the time I get on the call, I know more about their business than most agencies would after a discovery session.
Results
After running this system for 90 days across three different service offers:
| Metric | Before (Manual) | After (Automated) |
|---|---|---|
| Leads researched/day | 15-20 | 200+ |
| Emails sent/day | 10-15 | 150-200 |
| Open rate | 18% | 52% |
| Reply rate | 2.1% | 8.7% |
| Meetings booked/week | 2-3 | 9-14 |
| Time spent daily | 3-4 hours | ~15 min (reviewing replies) |
| Monthly cost | $4,000 (SDR) | ~$47 (API costs) |
The open rate jump comes from two things: better subject lines (AI-generated, specific to the business) and proper domain warming. The reply rate improvement is almost entirely from the depth of personalization.
What I Would Do Differently
Start with domain warming. I burned my first domain by sending too aggressively in week one. Warm up over 2-3 weeks minimum.
Use multiple sending domains. Spread volume across 3-4 domains to reduce risk. Rotate them in the sending workflow.
Log everything. Every API call, every email sent, every score generated — it all goes into Postgres. When something breaks at 2 AM, you need the audit trail.
Get the Templates
I packaged all of these workflows as importable n8n templates — the scraper, enrichment pipeline, scoring system, email writer, sending sequences, and reply handler. Each one is documented with setup instructions and the prompt engineering that took weeks of iteration to get right.
You can grab them at whop.com/jbhflow.
If you have questions about the setup or want to see the system in action, find me on X at @LdYossi or check out jbhflow.com.
Top comments (0)