You know who you want to reach. You know where they work. You do not have their email address.
This is one of the most common problems in B2B, and there are about six ways to solve it. They differ enormously in accuracy, effort, and how badly they fail when they fail. Most people learn two of them and stop.
This guide covers all six, honestly, including when the free approaches are the right answer. It also covers the thing that matters more than any of them, which is what you do after you have a candidate address.
Method 1: Pattern guessing
The most common approach, and the one most likely to burn your sending reputation.
Companies use a small number of address formats. Work out which one a company uses, apply it to your target's name, and you have a candidate:
| Pattern | Example |
|---|---|
first.last@ | [email protected] |
first@ | [email protected] |
flast@ | [email protected] |
firstl@ | [email protected] |
first_last@ | [email protected] |
lastf@ | [email protected] |
When it works: you already know one confirmed address at that company, the company is small enough to use one consistent pattern, and the name is simple.
Why it fails more than people expect:
Large companies do not have one pattern. They have several, accumulated through acquisitions, regional IT policies, and collision handling. The second Jane Doe to join does not get jane.doe@, she gets something else, and you have no way to know which.
Names break the assumption constantly. Compound surnames, particles like "van der", accented characters, transliterated names, people who go by a middle name, and anyone whose display name is not their legal name. Every one of those produces a wrong guess that looks perfectly plausible.
And the failure is silent. A guessed address at a catch-all domain will accept your mail, look delivered, and vanish. You will never learn it was wrong, which means you will keep using the pattern.
The rule: pattern guessing without verification is not a method, it is a bounce generator. If you use it, treat every output as a candidate to be checked, never as an address to send to.
Method 2: Search operators
Free, slow, and genuinely useful for a handful of high-value targets.
Addresses leak into public documents constantly: conference speaker lists, academic papers, press releases, support forum posts, GitHub commit metadata, PDF contact sheets, and old job postings.
Useful search patterns:
1"firstname lastname" "@company.com"
2site:company.com "@company.com" contact
3"[email protected]"
4filetype:pdf "company.com" "firstname lastname"When it works: the person is somewhat public-facing, has spoken at an event, contributed to open source, or been quoted in press.
Limitations: it does not scale past a few targets an hour, it skews heavily toward senior and technical people, and addresses found this way are often years old. A @company.com address from a 2019 conference program is evidence the person worked there in 2019.
Worth doing for a genuinely important prospect. Not worth doing for a list.
Method 3: Company-published sources
Sometimes the company simply tells you.
Check the website footer, the contact page, press and media pages, investor relations, careers pages, and any documentation site. Press contacts and investor relations addresses are almost always published and usually real.
Two caveats. Most of what you find this way is a role account (press@, sales@, info@), which is a queue rather than a person and behaves very differently in outreach. And for anything other than press or support, the published address is rarely the person you actually want.
Genuinely useful when you need to reach a company rather than an individual, which is more often than people admit.
Method 4: Ask, or go around
The unglamorous method that outperforms all of the above for high-value targets.
If someone has a contact form, use it. If a colleague can introduce you, that is worth more than any address. If the person is active on a public platform, a short message there asking where to send something is often faster than finding the address and considerably more likely to get a reply.
This does not scale. It also has by far the highest response rate per unit of effort, so for a list of twenty genuinely important targets it is frequently the correct approach and everyone skips it because it feels like less leverage.
Method 5: Profile resolution
Work backwards from a professional profile rather than forwards from a name.
The logic is different from pattern guessing. Rather than starting with a name string and a domain and constructing a candidate, you start with a resolved identity, which carries the correct legal name spelling, the current employer, and the employment status. That removes the two biggest sources of guessing error: wrong name form and wrong company.
This matters most for the cases where pattern guessing is weakest. Someone who goes by a shortened name, someone with a compound surname, someone who changed employer four months ago. A resolved profile knows those things; a name string does not.
If you want to see it work on a single person, there is a free tool that will turn a profile URL into a verified work email without any setup.
Method 6: An email finder API
For anything at volume, this is the answer, and it combines the previous methods programmatically.
You send a name and a company domain. You get back an address, with an indication of confidence, checked against the receiving mail server rather than merely constructed.
1from renidly import Renidly
2
3renidly = Renidly() # reads RENIDLY_API_KEY from the environment
4
5found = renidly.emails.find(
6 first_name="Jane",
7 last_name="Doe",
8 domain="northwind-logistics.com", # bare hostname, no scheme, no @
9)
10
11if found.found and found.confidence == "high":
12 queue_for_outreach(found.email)If your stack is Node, the same call, and note that the domain parameter is strict in both languages:
1import { Renidly } from "renidly";
2
3const renidly = new Renidly();
4
5const found = await renidly.emails.find({
6 firstName: "Jane",
7 lastName: "Doe",
8 domain: "northwind-logistics.com",
9});
10
11if (found.found && found.confidence === "high") {
12 await queueForOutreach(found.email);
13}That domain parameter trips up most first integrations. It wants a bare hostname containing a dot. Passing https://northwind-logistics.com or [email protected] returns a validation error naming the offending field. User-entered company fields are full of full URLs, so normalize before the call rather than handling the error after it.
Two related endpoints worth knowing:
From a profile URL. emails.find_by_url(url) in Python, emails.findByUrl(url) in Node, takes a profile URL or bare handle and returns the address plus the resolved first name, last name, domain and company. This is method 5, automated.
Everyone at a domain. emails.prospects(domain, kind) pages through known addresses at a company, where kind is full or verified_only. Useful when you want coverage of an account rather than one named person. It bills per address returned rather than per call, so page deliberately and stop when you have enough.
1cursor = None
2while True:
3 page = renidly.emails.prospects("northwind-logistics.com", "verified_only", cursor=cursor)
4 for p in page.prospects:
5 handle(p.email, p.first_name, p.last_name)
6 if not page.next_cursor:
7 break
8 cursor = page.next_cursorPagination uses opaque cursors. Pass next_cursor back exactly as received and never construct one yourself.
The six methods compared
| Method | Accuracy | Scales | Effort | Best for |
|---|---|---|---|---|
| Pattern guessing | Low without verification | Yes | Low | Never alone |
| Search operators | High when found | No | High | A few key targets |
| Company sources | High | No | Low | Reaching a company, not a person |
| Ask or go around | Highest response rate | No | Medium | Top twenty accounts |
| Profile resolution | High | Partially | Low | Hard names, recent movers |
| Finder API | High | Yes | Low | Any list |
The step that matters more than finding
Here is the part most guides skip, and it is the difference between a working process and a damaged sending domain.
Finding an address and confirming it works are two separate operations. A candidate address, however you obtained it, is a hypothesis. Verification tests it against the receiving mail server before you send anything.
1check = renidly.emails.verify(found.email)
2
3if check.deliverable and not check.catch_all:
4 send(found.email)1const check = await renidly.emails.verify(found.email);
2
3if (check.deliverable && !check.catchAll) {
4 await send(found.email);
5}The catch_all flag is the one that causes delayed damage. A catch-all domain accepts mail for every address, whether or not the mailbox exists, which means a guessed address there comes back looking deliverable and then disappears. Bounces arrive weeks later, mixed into normal sending, by which point your sender reputation has already absorbed the hit. And reputation damage suppresses delivery of mail that would otherwise have landed, so the cost is not just the bad addresses.
Catch-all is common at exactly the mid-market and enterprise companies most B2B teams target. Treat it as its own segment with its own sending schedule and its own bounce monitoring, never as part of your clean list.
The full breakdown of what each verification verdict means and how to route on it is in the email verification API guide, and you can check a single address in the free email verifier to see a real catch-all result next to a real confirmed mailbox.
The other question nobody asks
You found an address. You verified it works. There is still a question outstanding: does this person still have this job?
An address can verify perfectly and belong to someone who left four months ago, because the mailbox is open and forwarding. Deliverable and correct are different properties, and outreach to someone who moved is worse than no outreach, because it tells the recipient your data is stale.
So the complete sequence for outbound is three steps, not two: resolve the person, confirm the address is deliverable, and confirm they are still in the role you think they are. That third step is a movement question, and the pattern for catching role changes before you send is in job change signals. For keeping an existing contact database current rather than checking one person, the field-level refresh approach is in CRM data decay.
If you are going the other direction, starting from an address you already have rather than looking for one, that is reverse email lookup.
Doing this for a list
For more than a handful of people, three things change.
Use batch, not a loop. Submitting up to 1000 at a time in one request is faster, does not generate a thousand requests to be rate limited, and gives you resumability if the process dies. The full pattern including resumable job handles is in batch enrichment at scale.
Budget on submissions, not results. Every lookup is billed, including the ones that resolve nothing, because determining that an address cannot be found takes the same work as finding one. If you submit 10,000 names and 60% resolve, your true cost per usable address is roughly 1.7 times the headline rate. Measure your resolution rate on a 500-record sample before sizing a full run.
Suppress permanent failures. Some people have opted out of resolution, and that is durable. Record the exclusion on first occurrence and never resubmit, or every run you ever execute will re-request the same records forever at full cost.
Per-route costs and the free tier are on the pricing page.
What not to do
Do not send to unverified guesses. This is the single most damaging habit in outbound. A 15% bounce rate on a small campaign can affect delivery for everything you send afterwards.
Do not treat role accounts as people. info@ and support@ are queues. They have different response dynamics and personalizing to them reads as automated.
Do not build a pattern database and trust it. Company patterns change, exceptions accumulate, and a stored pattern that was right in 2024 will produce confident wrong answers today.
Do not scrape addresses from places that disallow it. Beyond the legal question, addresses harvested that way are disproportionately old, role-based, or spam traps, and spam traps are specifically designed to damage senders who acquired lists rather than building them.
The one-line version
Pattern guessing is a hypothesis generator, not a method. Search operators and asking directly beat everything else for your top twenty targets. For anything at list scale, use a finder API and then verify separately, because finding and confirming are different operations and skipping the second one is what damages sending domains.
And remember the third question: an address that is deliverable can still belong to someone who left. Resolve, verify, then confirm the role.
If you want to try the finding step on one person before building anything, the free email finder takes a name and a domain, and the full set of free tools covers verification and reverse lookup too, with no signup. For anything programmatic, the Email API documentation and the Quickstart take about five minutes.


