Email Finding

get_email is the only Email-category tool. It takes a name and a domain, generates every common email pattern, and verifies each against the domain’s mail server via SMTP — no mail is actually sent. This guide covers the patterns and pitfalls of running it at scale.

Basic usage

$curl -X POST https://api.gtm-tools.sh/api/v0/get_email \
> -H "Authorization: Bearer $GTM_TOOLS_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "name": "Justin Mares",
> "domain": "kettleandfire.com",
> "input_parameters": {"source": "linkedin", "lead_id": "abc123"}
> }'
1{
2 "email": "justin@kettleandfire.com",
3 "is_catch_all": false,
4 "domain": "kettleandfire.com",
5 "input_parameters": {"source": "linkedin", "lead_id": "abc123"}
6}

input_parameters is echoed back in the response — useful for batching where you correlate by lead_id.

Handling catch-all domains

Catch-all domains accept SMTP traffic for every local-part, so verification can’t disambiguate the real address. The response sets is_catch_all: true and still returns the most likely candidate (by pattern frequency).

1{
2 "email": "j.mares@bigcorp.com",
3 "is_catch_all": true,
4 "domain": "bigcorp.com"
5}

When you see is_catch_all: true:

  • Don’t blast. A bounce won’t tell you the address is wrong because the server accepts everything.
  • Cross-check against LinkedIn. Look for the email on the profile, in a press release, or in the SignalHire-style social header.
  • Use a soft probe. Send a low-stakes message (e.g. an SDR cold email rather than a procurement contract) so the cost of being wrong is small.

Pattern coverage

get_email tries every common pattern, including:

first@domain.com → justin@kettleandfire.com
last@domain.com → mares@kettleandfire.com
firstlast@domain.com → justinmares@kettleandfire.com
first.last@domain.com → justin.mares@kettleandfire.com
flast@domain.com → jmares@kettleandfire.com
f.last@domain.com → j.mares@kettleandfire.com
firstl@domain.com → justinm@kettleandfire.com
first_last@domain.com → justin_mares@kettleandfire.com
last.first@domain.com → mares.justin@kettleandfire.com

Each candidate gets one SMTP verification round-trip. The first deliverable hit is returned.

Latency

Expect 2–5 seconds per call. The SMTP handshake to the domain’s MX records is the slow part — it’s network-bound. For batch jobs:

  • Run requests in parallel (small concurrency, e.g. 5–10).
  • Don’t expect sub-second responses; budget at least 3s p50.

Batching

1const KEY = process.env.GTM_TOOLS_API_KEY!;
2
3interface Lead {
4 id: string;
5 name: string;
6 domain: string;
7}
8
9async function findEmails(leads: Lead[]) {
10 const results = await Promise.all(
11 leads.map(async (lead) => {
12 const res = await fetch("https://api.gtm-tools.sh/api/v0/get_email", {
13 method: "POST",
14 headers: {
15 "Authorization": `Bearer ${KEY}`,
16 "Content-Type": "application/json",
17 },
18 body: JSON.stringify({
19 name: lead.name,
20 domain: lead.domain,
21 input_parameters: { lead_id: lead.id },
22 }),
23 });
24 return res.ok ? res.json() : null;
25 })
26 );
27 return results.filter(Boolean);
28}

Concurrency above ~10 requests will hit rate limits. Honor Retry-After on 429 responses.

When to skip get_email

SituationBetter tool
You already have a LinkedIn profile URLJust message via send_linkedin_message (5 tokens vs 5 + the time cost)
You only need a company-level contactUse info@ or contact@ directly — no lookup required
The lead’s is_catch_all was already setDon’t burn another 5 tokens to confirm catch-all status

Failure cases

ResultMeaning
email: null, is_catch_all: falseNo pattern verified. The person likely doesn’t have an email at this domain (wrong company? freelancer?).
email: <addr>, is_catch_all: truePattern returned by frequency, not verified. Treat as hypothesis.
email: <addr>, is_catch_all: falseVerified deliverable. High confidence.
5xx errorUpstream verifier issue. Retry with backoff — tokens are refunded automatically.

Token cost

5 tokens per call. A failed verification still costs 5 tokens (the SMTP handshakes ran), but 5xx upstream errors trigger an automatic refund.

Next steps