Waterfall Enrichment for Email Finding: Maximize Coverage with Multi-Provider Strategies
How to chain multiple email finding providers in a waterfall to maximize coverage from 60% to 85%+. Includes step-by-step Clay setup, provider ordering strategies, and real results.
The Single-Provider Problem
Here is a frustrating reality of B2B email finding: no single provider can find emails for more than ~85% of your prospect list. In our 2026 benchmark, the highest coverage from any single tool was 88.2% (Apollo), and the highest accuracy tool (Findymail) covered 78.4%.
That means for every 100 prospects you search, 12-22 come back empty. If your prospect list has 5,000 people, that is 600-1,100 contacts you cannot reach.
Each email provider has different data sources, different crawling patterns, and different databases. Provider A might have the email for a VP at Stripe that Provider B missed, while Provider B has the email for a director at Notion that Provider A does not have. Their coverage is partially overlapping but never identical.
Waterfall enrichment solves this by chaining providers together: try Provider A first, then pass any “not found” results to Provider B, then to Provider C, and so on. Each subsequent provider fills in gaps the previous ones missed.
How Waterfall Enrichment Works
The concept is straightforward:
Input: 1,000 prospects (name + company)
|
v
[Provider 1: Findymail]
Found: 784 emails (78.4%)
Not found: 216
|
v
[Provider 2: Apollo]
From 216 remaining:
Found: 128 additional emails
Not found: 88
|
v
[Provider 3: Hunter]
From 88 remaining:
Found: 42 additional emails
Not found: 46
|
v
Final result: 954 emails found (95.4% coverage)
The numbers above are illustrative, but the pattern is real. In practice, we consistently see waterfall approaches increase coverage by 15-25 percentage points compared to a single provider.
Why Provider Order Matters
Waterfall enrichment is not just about which providers you use — the order matters significantly because of two competing goals:
- Accuracy: You want to use the most accurate provider first, because its results will be trusted without a second opinion.
- Cost: You want to use the cheapest provider first to minimize spend, since subsequent providers only process the remaining (smaller) set.
These goals often conflict. The most accurate provider is rarely the cheapest. Here is how to think about ordering:
Accuracy-first ordering (recommended for cold outreach):
| Position | Provider | Rationale |
|---|---|---|
| 1st | Findymail | Highest accuracy (93.2%), built-in verification |
| 2nd | Hunter | Strong accuracy (84.7%), transparent confidence scores |
| 3rd | Apollo | Broadest coverage (88.2%), catches remaining gaps |
Cost-first ordering (for budget-sensitive operations):
| Position | Provider | Rationale |
|---|---|---|
| 1st | Apollo | Cheapest per-email, generous free tier |
| 2nd | Snov.io | Low cost, decent coverage |
| 3rd | Findymail | High accuracy for remaining hard-to-find contacts |
The accuracy-first approach is almost always better. Here is why: the emails you find in position 1 make up the bulk of your results (typically 70-80%). If your first-position provider has 93% accuracy versus 81%, that 12-point difference applies to the majority of your results. Saving a few cents per email in position 1 but getting worse data is a false economy.
Building a Waterfall in Clay (Step by Step)
Clay is the most popular platform for building enrichment waterfalls because it provides no-code integrations with dozens of data providers. Here is how to set one up.
Step 1: Import Your Prospect List
Upload a CSV or connect Clay to your CRM. Your input data needs at minimum:
- Full name (or first name + last name in separate columns)
- Company name or company domain
- LinkedIn URL (optional, but improves accuracy for some providers)
If you only have company names without domains, add a “Find Company Domain” enrichment step first. Clay has built-in domain resolution.
Step 2: Add Your First Email Provider
Click “Add Enrichment” and select your first-position provider (we recommend Findymail for accuracy-first waterfalls).
Configure the enrichment:
- Input mapping: Map your name and company columns to the provider’s required fields
- Output column: Name it something clear like
email_provider_1 - Run condition: Run for all rows (this is the first provider, no filtering needed)
Run the enrichment. Wait for results.
Step 3: Add Your Second Provider with Conditional Logic
Add a second enrichment step for your second provider (e.g., Hunter).
Critical configuration: Set the run condition to only process rows where the first provider returned no result:
Run when: {email_provider_1} is empty
This is the “waterfall” logic. Provider 2 only runs on contacts that Provider 1 missed. This saves credits and avoids paying twice for the same contact.
Map the inputs the same way as Step 2. Name the output column email_provider_2.
Step 4: Add Your Third Provider
Same process as Step 3, but the run condition checks both previous columns:
Run when: {email_provider_1} is empty AND {email_provider_2} is empty
Name the output email_provider_3.
Step 5: Create a Unified Email Column
Add a formula column that consolidates all three provider columns into a single email field:
Formula: COALESCE({email_provider_1}, {email_provider_2}, {email_provider_3})
Or in Clay’s formula syntax:
IF({email_provider_1} != "", {email_provider_1},
IF({email_provider_2} != "", {email_provider_2},
{email_provider_3}))
This gives you one clean email column with the best available result for each contact.
Step 6: Add Verification (If Needed)
If your first-position provider (e.g., Findymail) includes built-in verification, those results are already verified. But emails from Provider 2 and 3 should be verified separately.
Add a verification enrichment step with this run condition:
Run when: {email_provider_1} is empty AND {final_email} is not empty
This only verifies emails that came from providers without built-in verification. Use ZeroBounce, NeverBounce, or another verification service (see our email verification guide for a comparison).
Step 7: Export Clean Results
Filter your table to only rows where:
final_emailis not empty, ANDverification_statusis “valid” (or the email came from a provider with built-in verification)
Export this filtered set to your CRM or outreach tool.
Building a Waterfall with Code
If you prefer programmatic control or need to process at high volume, here is a Python implementation:
import time
from dataclasses import dataclass
from typing import Optional
@dataclass
class EmailResult:
email: Optional[str]
provider: str
confidence: float
verified: bool
class WaterfallEnricher:
def __init__(self, providers):
"""
providers: List of dicts with keys:
- name: str
- find_fn: callable(first_name, last_name, domain) -> dict
- has_verification: bool
- cost_per_lookup: float
"""
self.providers = providers
self.stats = {p["name"]: {"attempted": 0, "found": 0} for p in providers}
def find_email(self, first_name, last_name, domain):
"""Try each provider in order until one returns a result."""
for provider in self.providers:
self.stats[provider["name"]]["attempted"] += 1
result = provider["find_fn"](first_name, last_name, domain)
if result and result.get("email"):
self.stats[provider["name"]]["found"] += 1
return EmailResult(
email=result["email"],
provider=provider["name"],
confidence=result.get("confidence", 0),
verified=provider["has_verification"],
)
time.sleep(0.2) # Brief pause between providers
return EmailResult(email=None, provider="none", confidence=0, verified=False)
def print_stats(self):
"""Print coverage statistics for each provider in the waterfall."""
total_attempted = self.stats[self.providers[0]["name"]]["attempted"]
total_found = sum(s["found"] for s in self.stats.values())
print(f"\n{'Provider':<20} {'Attempted':<12} {'Found':<10} {'Contribution'}")
print("-" * 60)
for provider in self.providers:
name = provider["name"]
stats = self.stats[name]
contribution = stats["found"] / total_attempted * 100 if total_attempted else 0
print(f"{name:<20} {stats['attempted']:<12} {stats['found']:<10} {contribution:.1f}%")
print("-" * 60)
print(f"{'TOTAL':<20} {total_attempted:<12} {total_found:<10} {total_found/total_attempted*100:.1f}%")
# Example usage with placeholder provider functions
enricher = WaterfallEnricher([
{
"name": "Findymail",
"find_fn": findymail_lookup, # Your API wrapper
"has_verification": True,
"cost_per_lookup": 0.05,
},
{
"name": "Hunter",
"find_fn": hunter_lookup, # Your API wrapper
"has_verification": False,
"cost_per_lookup": 0.045,
},
{
"name": "Apollo",
"find_fn": apollo_lookup, # Your API wrapper
"has_verification": False,
"cost_per_lookup": 0.03,
},
])
# Process contacts
for contact in contacts:
result = enricher.find_email(
contact["first_name"],
contact["last_name"],
contact["domain"],
)
if result.email:
print(f"Found: {result.email} (via {result.provider})")
enricher.print_stats()
Real Results: What Waterfall Enrichment Actually Achieves
We ran our 500-contact benchmark dataset through three different waterfall configurations. Here are the actual results:
Configuration A: Findymail -> Hunter -> Apollo
| Metric | Provider 1 (Findymail) | + Provider 2 (Hunter) | + Provider 3 (Apollo) |
|---|---|---|---|
| Cumulative coverage | 78.4% | 87.6% | 93.8% |
| New emails added | 392 | 46 | 31 |
| Cumulative accuracy | 93.2% | 91.8% | 90.1% |
| Total cost | $19.60 | $21.67 | $22.60 |
Configuration B: Apollo -> Snov.io -> Findymail
| Metric | Provider 1 (Apollo) | + Provider 2 (Snov.io) | + Provider 3 (Findymail) |
|---|---|---|---|
| Cumulative coverage | 88.2% | 91.4% | 94.2% |
| New emails added | 441 | 16 | 14 |
| Cumulative accuracy | 81.3% | 80.7% | 82.1% |
| Total cost | $13.23 | $13.71 | $14.41 |
Configuration C: Findymail -> Apollo -> RocketReach
| Metric | Provider 1 (Findymail) | + Provider 2 (Apollo) | + Provider 3 (RocketReach) |
|---|---|---|---|
| Cumulative coverage | 78.4% | 91.0% | 94.6% |
| New emails added | 392 | 63 | 18 |
| Cumulative accuracy | 93.2% | 90.4% | 89.7% |
| Total cost | $19.60 | $21.49 | $23.11 |
Key takeaways from the results:
- All configurations reached 93-95% coverage, dramatically outperforming any single provider.
- Configuration A (Findymail -> Hunter -> Apollo) had the best accuracy at 90.1%, because the first provider (which contributed most emails) had the highest accuracy.
- Configuration B was cheapest ($14.41 total) but had the lowest accuracy at 82.1%, because Apollo (first position) has lower accuracy and its emails made up the bulk of results.
- The third provider added diminishing returns in every configuration — typically only 14-31 additional emails. Two providers capture most of the incremental value.
- Cost per additional email increases sharply after the first provider. Provider 1 cost ~$0.05 per email. Provider 3 cost ~$0.05 per email but only for 14-31 emails, meaning the marginal cost is low in absolute terms even if the per-unit is higher.
Provider Ordering Strategy Matrix
Use this decision matrix to choose your waterfall configuration:
| Priority | Position 1 | Position 2 | Position 3 | Expected Coverage | Expected Accuracy |
|---|---|---|---|---|---|
| Accuracy first | Findymail | Hunter | Apollo | ~94% | ~90% |
| Coverage first | Apollo | Findymail | RocketReach | ~95% | ~85% |
| Budget first | Apollo (free tier) | Snov.io | Hunter | ~93% | ~82% |
| Enterprise focus | Lusha | Findymail | Apollo | ~92% | ~89% |
| EU/GDPR focus | Findymail | Kaspr | Hunter | ~90% | ~88% |
For details on individual providers, see our guides on Hunter.io and Apollo.io.
Cost Optimization Tips
1. Always Filter Before Enriching
Before running contacts through your waterfall, filter out:
- Contacts you already have emails for in your CRM
- Contacts at companies outside your ICP
- Duplicate entries
- Contacts without enough identifying information (no company, no last name)
Every lookup costs money. Do not pay to enrich contacts you will never email.
2. Use Free Tiers Strategically
Apollo offers 10,000 free credits per month. If your monthly volume is under 10,000, you can use Apollo as your first-pass provider at zero cost, then use a paid provider for the remaining “not found” results. This alone can cut your enrichment costs by 60-70%.
3. Cache Results
Email addresses do not change frequently. Cache your lookup results and skip re-enrichment for contacts you have already found within the last 90 days.
import json
import os
from datetime import datetime, timedelta
CACHE_FILE = "email_cache.json"
CACHE_TTL_DAYS = 90
def load_cache():
if os.path.exists(CACHE_FILE):
with open(CACHE_FILE) as f:
return json.load(f)
return {}
def save_cache(cache):
with open(CACHE_FILE, "w") as f:
json.dump(cache, f)
def get_cached_email(name, domain):
cache = load_cache()
key = f"{name.lower()}|{domain.lower()}"
if key in cache:
entry = cache[key]
cached_date = datetime.fromisoformat(entry["date"])
if datetime.now() - cached_date < timedelta(days=CACHE_TTL_DAYS):
return entry["email"]
return None
def cache_email(name, domain, email):
cache = load_cache()
key = f"{name.lower()}|{domain.lower()}"
cache[key] = {"email": email, "date": datetime.now().isoformat()}
save_cache(cache)
4. Set Confidence Thresholds Between Providers
Instead of a binary “found or not found” waterfall, use confidence scores:
If Provider 1 returns an email with confidence >= 90%: accept it
If Provider 1 returns an email with confidence 70-89%: accept but flag for verification
If Provider 1 returns an email with confidence < 70%: pass to Provider 2
If Provider 1 returns nothing: pass to Provider 2
This prevents accepting low-confidence guesses from your first provider when a second provider might have better data.
5. Monitor Provider Performance Over Time
Provider accuracy is not static. Databases get refreshed, algorithms change, companies update their data sources. Track your per-provider metrics monthly:
| Month | Provider | Emails Found | Bounced | Accuracy |
|---|---|---|---|---|
| Jan 2026 | Findymail | 2,340 | 28 | 98.8% |
| Jan 2026 | Hunter | 456 | 31 | 93.2% |
| Jan 2026 | Apollo | 312 | 42 | 86.5% |
| Feb 2026 | Findymail | 2,512 | 32 | 98.7% |
| … | … | … | … | … |
If a provider’s accuracy drops significantly, consider reordering your waterfall or replacing them.
When Waterfall Enrichment Is Not Worth It
Waterfall enrichment adds complexity and cost. It is not always justified:
- Low volume (under 200 contacts/month): A single good provider is simpler and sufficient. The incremental coverage from a waterfall does not justify the setup overhead.
- Non-critical outreach: If you are sending a newsletter or marketing emails to opted-in contacts, a single provider with verification is fine.
- Tight budget with no room for multiple subscriptions: Two provider subscriptions cost more than one. If budget is hard-capped, invest in one high-accuracy provider instead.
- One-time enrichment: If you need to enrich a list once and never again, manually running “not found” contacts through a second tool is easier than setting up a waterfall pipeline.
Bottom Line
Waterfall enrichment is the most effective way to maximize your email finding coverage without sacrificing accuracy. By chaining providers in the right order — accuracy-first for outreach, cost-first for budget operations — you can consistently reach 90-95% coverage where a single provider would top out at 78-88%.
The setup takes 30-60 minutes in Clay, or a few hours if you are building a custom pipeline. The ongoing cost per additional email is modest. And the coverage improvement is dramatic.
Start with two providers. Add a third only if the incremental coverage justifies the cost and complexity. Verify everything that is not already verified. And track your per-provider metrics so you can optimize over time.
For provider-specific setup details, see our guides on Hunter.io and Apollo.io. For verification best practices, see our email verification guide.