DEV Community

Nikita Iakovlev
Nikita Iakovlev

Posted on

Three of Five Founder Mailboxes Rejected first.last, the Layout Everyone Defaults To

I run a visa agency in Bali, and the other half of my week goes into Apify Actors. One of them is narrow on purpose: you give it a LinkedIn profile URL, it gives you back the person's name, title, current company, location — and their work e-mail address with the mailbox checked over SMTP.

The part that looks like the hard problem is finding the address. It isn't. Writing first.last@domain.com takes one line of code, and that is exactly what most of this category ships, with a label like deliverable next to it. The hard problem is the one that appears the moment you actually ask a mail server whether the thing you just wrote down exists: you only get a couple of questions, so the order you ask them in is the product.

Here is what that costs, measured on nine runs between 10 and 17 October 2026 across six public profiles of founders and executives (builds 0.1.32 to 0.1.41).

The ranked guess was wrong more often than it was right

Every one of the six people got the same treatment: read the public profile, resolve the company to a domain, build candidate addresses in the company's own layout, then check mailboxes over SMTP — at most two per person — and keep the first one the server confirms.

Person Domain Check 1 (ranked first) Check 2 Delivered
Bill Gates gatesfoundation.org bill.gates@ → safe — bill.gates@
Garry Tan ycombinator.com garry.tan@ → safe — garry.tan@
Satya Nadella microsoft.com satya.nadella@ → invalid satya@ → safe satya@
Rand Fishkin sparktoro.com rand.fishkin@ → invalid rand@ → safe rand@
Jeff Weiner nextplayventures.com jeff.weiner@ → invalid jeff@ → safe jeff@
Patrick Collison stripe.com patrick.collison@ → catch_all patrick.collison@arcinstitute.org → catch_all nothing

Five of the six ended with a mailbox the server confirmed. In three of those five the highest-ranked candidate came back invalid — not "unconfirmed", not "probably", but a mail server actively saying there is no such mailbox. The address that worked was the plain first name.

Six people is not a statistical law about corporate mail, and I'm not going to dress it up as one. It is, however, a direct cost argument. If your highest-ranked candidate fails 60% of the time on the kind of person people actually pay to reach, then a tool with no mailbox check hands out three wrong addresses in five with a confident label on each, and a tool with a mailbox check spends 60% of its check budget on check 1.

Why the default led in all six cases

The Actor's first choice of layout is not supposed to be a default. It's supposed to be evidence:

// The whole product rests on one observation: a company that publishes any personal
// address on its own website has told you the rule for all of them. jane.doe@acme.com
// on the team page means the sales director is firstname.lastname too.
Enter fullscreen mode Exit fullscreen mode

If the company site publishes j.diaz@acme.com and m.kent@acme.com, you don't guess — you've been told the layout, and you write the one address you were asked for in it. That path works, and I wrote about the evidence side of it separately. It just didn't fire once in this batch. All six rows came back with patternEvidenceCount: 0 and emailPatternSource: "default": gatesfoundation.org, microsoft.com, stripe.com, ycombinator.com, sparktoro.com and nextplayventures.com publish no personal address a crawler can read. Big companies route you to a contact form; two-person funds have a hello@.

With nothing to learn from, the ranked prior takes over, and the prior is this list:

export const PATTERNS = [
    'first.last', 'first', 'flast', 'firstlast', 'first_last', 'firstl', 'f.last',
    'last.first', 'lastfirst', 'first-last', 'last', 'fl',
];
Enter fullscreen mode Exit fullscreen mode

first.last is roughly half of corporate mail, so it leads. But look at who rejected it: the CEO who joined in 1992, the founder of a two-person company, a founding partner of his own fund. People who were employee number small tend to hold the short mailbox, and the short mailbox is first@. The population you want from LinkedIn — founders, partners, heads of things — is enriched in exactly the people for whom the most common layout is the wrong one.

The budget is two checks, and that's the whole design

const REOON_MAX_CHECKS = 2;
…
const pool = candidatesFor(first, last, company.domain, company.pattern || 'first.last', …);
const order = [...(onSite ? [onSite] : []), ...pool.filter((c) => c.email !== onSite?.email)];
v = await verifyFirst(order.map((c) => c.email), { maxChecks: REOON_MAX_CHECKS });
Enter fullscreen mode Exit fullscreen mode

Two, not ten, for two unglamorous reasons. Each check is a paid API call, and the Actor charges per found e-mail — so checks that find nothing come out of my margin, not the buyer's bill. And hammering one domain with ten mailbox probes per person is how a verification provider's IPs stop being trusted by that domain's mail server.

Which means the ranking is not a nicety. With a budget of two, "the right layout is third in my list" and "I have no address for this person" are the same outcome. The three saves above all happened on check 2. There is no check 3.

That also explains a field people ask about. alternatives in every row is not padding — it's the rest of the ranked list that never got measured, labelled as such, so a buyer whose first address bounces has somewhere to go:

"email": "satya@microsoft.com",
"emailPattern": "first",
"confidence": "verified",
"emailStatus": "mailbox-verified",
"emailVerification": { "provider": "reoon", "status": "safe", "score": 98, "checks": 2,
  "tried": [ { "email": "satya.nadella@microsoft.com", "status": "invalid" },
             { "email": "satya@microsoft.com", "status": "safe" } ] },
"alternatives": [ { "email": "satya.nadella@microsoft.com", "pattern": "first.last" },
                  { "email": "snadella@microsoft.com", "pattern": "flast" } ]
Enter fullscreen mode Exit fullscreen mode

Where no amount of check budget helps

stripe.com answered catch_all to patrick.collison@stripe.com in all six runs it appeared in, with a score of 75 against the 98 a confirmed mailbox gets. A catch-all server accepts every address you offer it, including ones you invent, so nothing can be confirmed there — not by me, not by any vendor, no matter what their UI says. The remaining check rolled to the person's other current company from the profile, arcinstitute.org, which is also catch-all. Two checks spent, nothing learned:

"email": null,
"emailGuess": "patrick.collison@stripe.com",
"emailCatchAll": true,
"emailStatus": "catch-all",
"summary": "Not delivered as an e-mail: the mail server of stripe.com accepts any address
            (catch-all), so no mailbox can be confirmed there; best unverified guess
            patrick.collison@stripe.com is in \"emailGuess\". This row was not charged."
Enter fullscreen mode Exit fullscreen mode

The guess still ships, in a differently-named field, free. If you're building this yourself, that split is the only honest shape I've found: one field for "a server confirmed this", a different field for "this is my best construction", and never the same field for both.

The failure that only shows up when you run the same thing twice

One run out of nine broke the pattern. On 13 October, microsoft.com answered unknown to satya.nadella@microsoft.com after a single check — no score, no verdict — where the eight other runs got a flat invalid on that same address. The row came back free, no e-mail, status mailbox-unknown.

That's the right outcome for billing and the wrong one for the buyer, and it exposes a limit I'd rather print than hide: because the run stopped at unknown instead of invalid, it never reached satya@, and emailGuess was filled with satya.nadella@microsoft.com — the exact address eight other runs proved does not exist. Nothing in a single row is wrong. The row simply can't know what a different run knew. If you're consuming SMTP verdicts in a pipeline, treat unknown as "ask again tomorrow", never as "no mailbox", and keep your own memory of addresses that came back invalid across runs.

Calling it

curl -X POST "https://api.apify.com/v2/acts/lergassy~linkedin-email-finder/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"profileUrls":["https://www.linkedin.com/in/satyanadella",
                      "https://www.linkedin.com/in/patrickcollison",
                      "https://www.linkedin.com/in/williamhgates"]}'
Enter fullscreen mode Exit fullscreen mode

Those three profiles took 14.6 seconds end to end in the 17 October run these numbers come from, and about $0.0006 of platform compute; the price is per e-mail found, and the two catch-all and unknown rows above cost nothing. It's LinkedIn Email Finder on Apify if you want the rows rather than the reasoning.

Where it doesn't fit: there's no login, so the roughly one profile in five that LinkedIn hides from anonymous readers needs an unblocking route and sometimes still doesn't open. If your target list is all large-enterprise domains, expect a pile of catch-all rows and plan on guesses. If you already have a name and a company domain and no LinkedIn URL, the profile read is wasted work — the name-plus-domain path is a different Actor. And if your list is twenty people, reading twenty company websites by hand will beat any of this.

What I'd keep even if you never run it: a mailbox check turns e-mail finding from a text-formatting exercise into a search problem with a budget. Spend the budget on the layout the company told you about. When it hasn't told you anything, know that your prior is tuned for the median employee, and the people you're looking for aren't median.

Top comments (0)