How to find verified work emails for cold outreach

Find verified work emails that never made it into public databases, in a way that is cheaper and more effective than Apollo/Hunter.

Illustration of hands holding a binoculars, with icons of email on the lens.

This is the follow-up to the last two posts on cold outreach and scaling personalization. This one covers the boring bottleneck that quietly kills campaigns: getting accurate, verified work emails without depending on exhausted public databases.

If an email is sitting in Apollo/Hunter, there’s a decent chance it’s already been hammered by 50 other sequences. What we want instead are emails those platforms never discovered in the first place.

Stop relying on public email databases

The problem is simple: the most accessible emails are the most contacted. When everyone is reaching the same address, yours gets lost in the noise.

Since everyone pulls from the same pool, the problem compounds. Every complaint, bounce, or “mark as spam” reduces deliverability for everyone who hits that address next.

The better strategy is finding emails that never made it into any public database. This involves

  1. Understanding common email patterns
  2. Generating variations systematically
  3. Validating at scale (either with your own setup or a managed service)

The code for this post is in this repo: GitHub. I’ll keep it updated as we extend the pipeline in future posts.


Part 1: Generating email address variations

Given a prospect’s first name, last name, and company domain, you can generate dozens of plausible addresses.

Companies usually standardize on 1-2 formats. Once you confirm one real employee email, you can often infer the pattern for entire departments.

Here are some of the most common email patterns:

PatternExamplePrevalence
firstname + @ + domainjohn@acme.comVery High
firstname.lastname + @ + domainjohn.doe@acme.comVery High
firstlast + @ + domainjohndoe@acme.comHigh
f.lastname + @ + domainj.doe@acme.comMedium
first_last + @ + domainjohn_doe@acme.comMedium
first-last + @ + domainjohn-doe@acme.comLow
firstname.lastinitial + @ + domainjohn.d@acme.comLow

And here is a script that generates emails based on the patterns above:

def generate_email_patterns(first_name, last_name, domain):
    first = first_name.lower().strip()
    last = last_name.lower().strip()
    patterns = [
        f"{first}@{domain}",
        f"{first}.{last}@{domain}",
        f"{first}{last}@{domain}",
        f"{first[0]}.{last}@{domain}",
        f"{first[0]}{last}@{domain}",
        f"{first}_{last}@{domain}",
        f"{first}-{last}@{domain}",
        f"{first}.{last[0]}@{domain}",
        f"{last}.{first}@{domain}",
    ]
    # Remove duplicates while preserving order
    seen = set()
    unique_patterns = []
    for pattern in patterns:
        if pattern not in seen:
            seen.add(pattern)
            unique_patterns.append(pattern)
    return unique_patterns

emails = generate_email_patterns("John", "Doe", "acme.com")

For hyphenated names like “Jean-Pierre” or accented names like “María José”, generate name variants:

import unicodedata

def generate_name_variants(first, last):
    variants = [
        (first, last),
        (first.replace("-", ""), last),  # Remove hyphen
        (first.split("-")[0], last),     # Use first part only
    ]

    # Handle accented characters: María -> Maria, José -> Jose
    if any(ord(c) > 127 for c in first + last):
        first_ascii = ''.join(
            c for c in unicodedata.normalize('NFD', first)
            if unicodedata.category(c) != 'Mn'
        )
        last_ascii = ''.join(
            c for c in unicodedata.normalize('NFD', last)
            if unicodedata.category(c) != 'Mn'
        )
        variants.append((first_ascii, last_ascii))

    # Remove duplicates while preserving order
    seen = set()
    unique_variants = []
    for variant in variants:
        if variant not in seen:
            seen.add(variant)
            unique_variants.append(variant)

    return unique_variants

Part 2: Email validation using SMTP

Don’t skip validation and blast 100 variations hoping one lands. That tanks your sender reputation fast. Validate first. Always.

How SMTP verification works

Email validation connects directly to the recipient’s mail server using SMTP. VRFY and EXPN are usually disabled, so validators rely on RCPT TO (the same command used during real email delivery) to check if a mailbox exists without sending anything (Source: RFC 5321 SMTP Specification).

The process:

  1. Resolve the mail server (MX record) for the domain
  2. Connect via SMTP to that server on port 25
  3. Issue an RCPT TO command with the test email address
  4. Server responds with 250 (mailbox exists) or 550+ (invalid)
  5. Disconnect without sending

Building your own email validator

If you just want results without the infrastructure overhead, use a third-party API like Reacher or Bulk Email Checker.

If you need more control or want a cheaper solution at scale, self-host Reacher. It handles 100K+ validations/month for under $10/month.

All you need is a VPS with port 25 open ($5-15/month) and Docker installed.

Can you run this locally instead? Technically yes, if your ISP allows outbound port 25. But most residential ISPs block it, and even when they don’t, home IPs face issues:

  • Mail servers may rate-limit or reject connections from residential IPs
  • Your IP could get flagged or blacklisted after bulk validation attempts
  • ISPs sometimes allow the TCP connection but block actual SMTP traffic

For testing, local works. For production validation at scale, use a VPS instead.

To check outbound port 25 access, use:

nc -zv -w 3 aspmx.l.google.com 25 2>&1 || echo "Port 25 connection failed"
# or
nmap -p 25 aspmx.l.google.com

Many providers block outbound port 25 to prevent spam. If blocked on a VPS, contact support with your legitimate use case (email validation service). Some approve within hours; others don’t support it at all.

Deploy Reacher using Docker:

docker run -d -p 8080:8080 reacherhq/backend:latest

# Test
curl -X POST http://localhost:8080/v0/check_email \
  -H "Content-Type: application/json" \
  -d '{"to_email": "test@example.com"}'

Handling catch-all addresses

Some companies run catch-all policies where any email to their domain gets accepted. Validators mark these as “risky” instead of “safe.”

You can still send to them, but there’s higher bounce risk. Either filter them out or accept the calculated risk depending on your strategy.

Part 3: Compliance and ethics

I’m not an attorney, so this isn’t legal advice. But validating mailbox existence via SMTP is legitimate and widely practiced. You’re using the same protocol mail servers use daily. You’re not:

  • Breaking into servers
  • Accessing mailbox contents
  • Spoofing emails
  • Violating terms of service (if you’re using a reputable validator)

Important technical clarification

There’s a common misconception about SMTP MAIL FROM. For validation specifically, the MAIL FROM address doesn’t need to match your actual sending domain. During RCPT TO verification (what validators use), servers typically don’t authenticate MAIL FROM. They respond based on whether the recipient mailbox exists (RFC 5321 SMTP).

However, for actual email campaigns:

  • MAIL FROM (Return-Path) must align with the From: header for DMARC to pass (DMARC Alignment Requirements)
  • SPF records authorize the sending IP for the MAIL FROM domain
  • DKIM signatures sign the message with your domain
  • DMARC policies enforce alignment between SPF/DKIM results and the visible From: address

Bottom line: validators can use any MAIL FROM because they’re not sending mail. But when you send campaigns, use your actual domain and align SPF/DKIM/DMARC properly.

Best practices

  1. Use a managed service if infrastructure isn’t your thing — Bulk Email Checker or Reacher’s hosted API means no VPS to manage
  2. Batch validate before outreach — test patterns in groups, not one-by-one
  3. Respect rate limits — don’t overwhelm mail servers; add 1-2 second delays between checks
  4. Monitor bounces — if bounce rates spike, pause and diagnose your targeting or list quality
  5. Handle unsubscribes immediately — respect complaints and remove addresses the moment they ask

Putting it all together

Here’s the end-to-end workflow:

  1. Generate — 7-8 email pattern variations for each prospect
  2. Batch validate — use self-hosted Reacher or a managed API
  3. Enrich — pull LinkedIn data using the pipeline from my previous post
  4. Personalize — generate message copy with AI (GPT-4.5, Sonnet, etc.)
  5. Send — from a verified sender domain with SPF/DKIM/DMARC aligned
  6. Track — opens, clicks, and replies (replies matter most)
  7. Follow up — with sequences for non-responders, rotating angles each touch

Validated emails + pattern generation + personalization + proper authentication is how outbound actually runs in 2025.


What’s next?

This is part of a series of posts on building a modern cold outreach system:

Coming soon:

I’ll show you how to build a 100% automated appointment booking pipeline, end-to-end:

  • Automatically researching leads from LinkedIn
  • Automatically sending emails + LinkedIn DMs as a complete outbound sequence to get appointments booked

All built with Python, open-source tools, and cheap “pay-per-result” services.

Subscribe to my newsletter

I send out a newsletter every week, usually on Thursdays, that's it!

You'll get two emails initially—a confirmation link to verify your subscription, followed by a welcome message. Thanks for joining!

You can read all of my previous issues here

Related Posts.