Case Study
Concept BuildCold Email Lead Recovery
Two connected n8n workflows that turn a raw Apollo/Apify lead scrape into personalized cold email drafts — including for the leads the scrape couldn't find an email for.
Concept Build — Not a Client Project
A self-directed build, not yet deployed for a named client. The writing half it sits on top of was tested against a real, verified 270-lead list, covered in The Cold Email Personalizer. The recovery chain described here is built and working.
At a Glance
01
Executive Summary
A lead scrape is not the same thing as a contactable lead. This build is two connected n8n workflows: one handles any lead that already has a usable email, and a second sits in front of it to recover the leads that arrive with none — through a two-tier lookup chain — before handing them to the same writing path.
Apollo and Apify scrapes typically return valid emails for less than half of scraped leads. That back half is the part most outbound setups write off. Here it gets a second and third shot: a LinkedIn URL lookup, then a company-domain lookup, both of which cost nothing unless they land. The leads that come back with an email rejoin the same writing path as everyone else.
02
The Problem
Apollo and Apify reliably return names, titles, and companies, but a working email on every row is the exception. Most outbound setups either drop the emailless rows on the floor or have someone manually chase them down one LinkedIn search at a time. Both options cost something: a smaller addressable list, or an operator's time spent on a problem a workflow could absorb.
- A scrape returns names, titles, and companies reliably — a working email on every row is the exception
- Dropping the emailless rows shrinks the addressable list before outreach even starts
- The manual alternative is chasing each one down through LinkedIn searches, one lead at a time
- Either way the cost lands somewhere: a smaller list, or an operator's hours on repetitive lookup work
03
The Approach
Split the problem into two workflows instead of one. The base flow handles any lead that already has a usable email: scrape the company site for context, write a personalized draft with GPT-4.1-mini, save it for review. The recovery flow sits in front of it and only handles the leads the base flow would otherwise never see. Everything downstream of “lead has an email” stays a single shared path, so the recovery logic can change without ever touching the writing logic.
Order the fallback chain cheapest-and-most-likely first. A LinkedIn URL lookup runs before a company-domain lookup, and both only run after the free scrape data has already come up short. Anymail Finder only charges a credit on a fully verified match, so a failed lookup at either tier costs nothing. The ordering decision means the system can afford to try every lead without inflating cost on the ones it cannot recover anyway.
Fail soft, not hard, at every optional step. A dead company website does not block the run, it just means a slightly more generic email. A failed LinkedIn lookup does not drop the lead, it falls through to the domain search. The only leads that end up with no draft are the ones that arrive with neither a LinkedIn URL nor a company domain — genuinely nothing left to try, not a workflow giving up early.
04
Architecture
| Layer | Implementation |
|---|---|
| Lead source | Google Sheets (Apollo/Apify export) |
| Orchestration | n8n — two workflows, manual trigger |
| Email recovery | Anymail Finder (LinkedIn-URL and domain+name lookups) |
| Personalization | OpenAI GPT-4.1-mini, structured JSON output |
| Context enrichment | Company website scrape, fails soft |
| Draft storage | Airtable (Full Name, Email, Title, Subject, Body) |
05
What It Does
06
Why This Matters
The interesting part is not the writing step, it is the ordering decision in front of it. Because a failed lookup costs nothing, the system can afford to try every lead. That single pricing detail is what makes recovering the back half of a scrape worth automating at all.
The instinct with a thin lead list is to buy more leads. Usually the cheaper move is to stop discarding the ones you already paid for. Half a scrape arriving without an email is not a data problem, it is a workflow that gives up one step too early. Two lookups and a fail-soft path are enough to close most of it.
Stack

