DEV Community

Nikita Iakovlev
Nikita Iakovlev

Posted on

Four Law Firms Published 79 Email Addresses. Four Tech Companies Published 11

I run a visa agency in Bali, and the other half of my week goes into Apify Actors. One of them does the thing every sales tool claims to do: you give it Jane Doe, acme.com, it gives you back Jane's work address.

Everybody solves this the same way — apply first.last, because roughly half of corporate mail is first.last, and ship it. That is a coin flip dressed as a lookup. The approach I wanted instead rests on one observation: a company that publishes any personal address on its own website has told you the rule for all of them. If the team page says michael.hart@acme.com, you no longer need to guess what the sales director's address looks like.

So the interesting question stopped being "which format should I guess" and became "how often does a company actually publish something I can read". I ran eight companies through it to find out, and the answer splits so hard by industry that it changes who this kind of tool is for.

The run

Eight people at eight companies, company rows on, four pages read per site:

{
  "people": [
    "Patrick Collison, stripe.com",
    "Jason Fried, basecamp.com",
    "Maria Bennett, cliffordchance.com",
    "Daniel Weber, freshfields.com",
    "Daniel Weber, linklaters.com",
    "Daniel Weber, bakermckenzie.com",
    "Daniel Weber, n8n.io",
    "Daniel Weber, hubspot.com"
  ],
  "candidatesPerPerson": 3,
  "checkMx": true,
  "includeCompanyRows": true,
  "maxPagesPerCompany": 4
}
Enter fullscreen mode Exit fullscreen mode

Two of those names are real people; "Maria Bennett" and "Daniel Weber" are probes I invented, because the person is not what is being measured here. The layout belongs to the company, and the same probe name at eight companies isolates it cleanly. 32 pages, plain HTTP, no browser, no proxy: 7.1 seconds and $0.29 for the whole run on 16 October 2026.

Here is what came back, per company:

Domain Addresses on site Usable as evidence Layout Confidence
cliffordchance.com 48 22 first.last high
freshfields.com 21 13 first.last high
linklaters.com 10 7 first.last high
bakermckenzie.com 0 0 — low
n8n.io 6 0 — low
stripe.com 4 1 first.last medium
basecamp.com 1 1 first confirmed
hubspot.com 0 0 — low

79 of the 90 published addresses came from the four law firms. 42 of the 44 usable pieces of evidence did too.

That is not a coincidence and it is not about law. Professional services firms publish staff directories because clients need to reach a named partner — and the same is true of agencies, clinics, brokerages, accountants and consultancies. Companies that sell software route everything through a form and a support queue on purpose. Which means: if your lead list is made of firms, this method mostly works. If it is made of SaaS companies, you are back to guessing, and the honest move is to say so in the output rather than to hide it.

90 addresses found, 44 that say anything

The gap between those two columns is where all the work is. An address only counts as evidence if you can tell which half is the first name — and that is harder than it sounds, because jane.diaz@ and complaints-in@ have the identical shape. One token, a separator, one token. A tool that reads shape alone will conclude that Stripe writes firstname-lastname on the strength of a complaints inbox, which is exactly what an early version of mine did.

So every candidate local part is tested against a list of common given names instead:

jane.diaz                    {"shapes":["first.last"],"strength":1}
esther.chancatlin            {"shapes":["first.last"],"strength":1}
victoria.brewertonowen       {"shapes":["first.last"],"strength":1}
jason                        {"shapes":["first"],"strength":1}
complaints-in                {"shapes":[],"strength":0}
nodes                        {"shapes":[],"strength":0}
nlinklaters                  {"shapes":[],"strength":0}
freshfieldsalumninetwork     {"shapes":[],"strength":0}
20iva.celic                  {"shapes":[],"strength":0}
esther.chan2                 {"shapes":[],"strength":0}
anne-marie.nistoroiu         {"shapes":[],"strength":0}
Enter fullscreen mode Exit fullscreen mode

Those are all real locals from this run. A few things worth pointing at:

esther.chancatlin and victoria.brewertonowen pass because the first half is a recognised name — the surname is never checked against anything, which is what lets compound surnames through intact.

20iva.celic is a scraping artifact: a page number glued to an address by the surrounding text. Clifford Chance's site produced both it and the clean iva.celic. The dirty one is refused by a leading-character rule, which is cheap and right. The clean one is also refused, because neither "iva" nor "celic" is in the given-name list — a false negative, and one I am fine with. The site offered 22 other usable addresses. Being wrong about the format is expensive; skipping one address out of 48 costs nothing.

anne-marie.nistoroiu is the one that annoys me. It contains both a dot and a hyphen, the dot wins the split, and anne-marie is not in the name list as a hyphenated token. It should be read as first.last and is not.

nlinklaters@linklaters.com is the kind of thing that makes you add rules you did not plan for: a token that starts with a letter, contains no separator, and is not a name. Read it as initial-plus-surname and you have "N. Linklaters" working at Linklaters.

The output ships patternEvidence and patternEvidenceCount for exactly this reason. 22 agreeing addresses and 1 are both patternSource: "site", and they are not the same claim.

Stripe's one piece of evidence is a placeholder

Stripe published four addresses: dpo@, privacy@, complaints-in@ — all discarded as department mail — and jane.diaz@stripe.com. So the Actor read Stripe's layout off a single address and returned:

{
  "fullName": "Patrick Collison",
  "email": "patrick.collison@stripe.com",
  "emailPattern": "first.last",
  "emailPatternSource": "site",
  "confidence": "medium",
  "patternEvidence": ["jane.diaz@stripe.com"],
  "patternEvidenceCount": 1,
  "emailStatus": "deliverable",
  "emailProvider": "google",
  "emailMxHost": "aspmx.l.google.com",
  "mailboxVerified": false,
  "alternatives": [
    { "email": "patrick@stripe.com", "pattern": "first" },
    { "email": "pcollison@stripe.com", "pattern": "flast" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Jane Diaz does not work at Stripe. She is a string in the home page's checkout graphic:

payment-graphic.checkout.email-address-us":"jane.diaz@stripe.com"
payment-graphic.checkout.email-address-de":"damian.michelfelder@example.com"
payment-graphic.checkout.email-address-jp":"taro.yamada@example.com"
Enter fullscreen mode Exit fullscreen mode

The German and Japanese variants of the same mockup use @example.com, which any sane filter drops. The US one uses the company's live domain. That slip is the entire reason Stripe has a site layout rather than a default guess.

The answer is probably still correct — first.last is a common layout and Stripe is a large US company. But it is right by luck, and a tool that reported it as "confirmed from the company's website" would be lying with a straight face. medium plus one visible piece of evidence lets you decide. That is the difference between a confidence column that means something and one that is decoration.

While I was in there: Freshfields came back as "companyName": "Leading global law firm". The name falls back through JSON-LD Organization → og:site_name → the first segment of <title>, and Freshfields has neither of the first two, so an SEO title became the company name. Two scraped fields, two reminders that "it parsed" and "it's true" are different states.

What zero evidence looks like

Baker McKenzie and HubSpot published nothing on four pages. n8n published six addresses — marketing@, license@, nodes@, community@, partners@, legal@ — and not one of them says anything about how a person's address is written. "How many addresses did you find" is the wrong metric; "how many contained a human name" is the one that matters.

All three got first.last, patternSource: "default", confidence: "low", and empty patternEvidence. Same shaped answer, honestly labelled as a guess. Sort by confidence and those rows go to the bottom of the sequence, or nowhere.

One more field earns its place here. The four law firms sit behind mail gateways — Mimecast twice, Proofpoint twice — while Stripe, HubSpot and n8n are on Google. emailProvider and emailMxHost come from an MX lookup, which also means mailboxVerified is permanently false: checking whether a specific mailbox exists requires an SMTP conversation on port 25, and outbound 25 is blocked from Apify's cloud for every Actor there, whatever a listing claims. Domain-level truth and published evidence is what is actually obtainable. If you need address hygiene on a list you already have — role flags, disposable domains, deliverability — that is a different job, and Email Verifier & Phone Number Validator is the one I use for it.

Calling it

from apify_client import ApifyClient

client = ApifyClient("<TOKEN>")
run = client.actor("lergassy/email-finder-api").call(run_input={
    "people": ["Daniel Weber, linklaters.com"],
    "includeCompanyRows": True,
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    if row["type"] == "person" and row["confidence"] in ("confirmed", "high"):
        print(row["email"], row["patternEvidenceCount"])
Enter fullscreen mode Exit fullscreen mode

peopleList takes pre-split fields from a CRM export, and inputDatasetId reads the people straight out of another Actor's dataset. Error rows are free, which matters when a third of a real list has an unresolvable company.

When not to use this

If your targets are consumer-facing tech companies, expect mostly low and treat the output as a guess with a mail-server check attached. If you need a confirmed mailbox before sending, no Apify Actor can give you that — send to the confirmed and high rows and let the bounce be the verification, with alternatives as the retry. And if what you actually want is every public address on a set of websites rather than one person's address, this is the wrong shape of tool entirely.

The Actor is Email Finder API. If you find a company whose layout it reads wrongly, the company and the expected address is a genuinely useful bug report — the given-name list and the rules above grew almost entirely out of those. anne-marie.nistoroiu is next.

Top comments (0)