Pre-Black Friday: 20% off, for as long as you stayEARLYBFon Solo and AgencyEnds Nov 28Start free

How-to guide

How to Decide Whether You Can Send an Email (Valid, Risky and Catch-All)

Four rules and four API calls. Sort any list by email status, size each bucket for free, and send each one on its own terms.

Get your free API key →Free to start. No credit card. 1,000 records to spend whenever you like.

You can send to verified and catch_all_valid addresses, and you should never send to invalid. Everything between is a separate batch: catch_all with care, risky, unknown, untested and derived only after a re-check. This guide sorts a real list into those buckets with the LeadOcean API.

TL;DR

  • Four rules cover the 13 email statuses: send, send small, check first, never send.
  • It takes about 20 minutes. Sizing every bucket is free. Pulling a 50-row sample spends 50 records.
  • The free plan has 1,000 records for life, enough to test every step here.

Prerequisites

  • A LeadOcean API key from app.leadocean.io, saved as $LEADOCEAN_API_KEY. The free plan has no card.
  • curl and jq.
  • A target list in mind. The examples use VPs of sales in Canada.
  • For the MCP route, the hosted server at https://api.leadocean.io/mcp signs you in with OAuth. No key is pasted.

Step 1: Learn the four rules

The status is a verdict about the mailbox, not about the person. Apply this table to every address before it reaches a sequencer.

StatusRuleWhy
verified, catch_all_validSendThe mailbox exists. catch_all_valid was confirmed through the provider's identity check on a catch-all domain.
catch_allSend small, separate batchThe domain accepts every name, so the mailbox cannot be confirmed.
risky, role, unknown, untested, derivedCheck firstA warning sign, a shared mailbox, an inconclusive check, or no check at all.
invalid, disposable, spam_trap, abuseNever sendBounces, or worse, a blocklisting.

Other verifiers use different words for the middle bucket and mostly say "do not send until resolved" (LeadMagic, October 2026). We split it further, because unknown and untested are not the same thing. untested means nobody checked. unknown means a check ran and could not decide. Both are listed in the full status guide.

Step 2: Size every bucket for free

Count each group before you pull a single row. Send count=true as a query parameter with limit 1. data comes back empty and the call costs nothing (LeadOcean API reference, October 2026).

bash
for S in '["verified"]' '["catch_all_valid","catch_all"]' '["risky","unknown"]'; do
  curl -s -X POST "https://api.leadocean.io/v1/people/search?count=true" \
    -H "x-api-key: $LEADOCEAN_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"jobLevel\":[\"VP\"],\"jobFunction\":[\"Sales & Business Development\"],\"country\":[\"CA\"],\"emailStatus\":$S,\"limit\":1}" \
  | jq -c '{status: "'"$S"'", total: .meta.total}'
done

The same count is one call in Claude or any MCP client:

code
Use leadocean_count_leads: jobLevel VP, jobFunction "Sales & Business Development",
country CA, emailStatus ["verified"], facets false.

meta.total is capped at 100,000. Enum values are exact strings, so check them with leadocean_list_enum_values before you guess.

Step 3: Pull the send list and keep the two safe statuses

Search with emailStatus set to the two send statuses. Filters are arrays, limit runs 1 to 100, and you page with cursor. Each person returned spends one record.

bash
curl -s -X POST "https://api.leadocean.io/v1/people/search" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jobLevel": ["VP"],
    "jobFunction": ["Sales & Business Development"],
    "country": ["CA"],
    "emailStatus": ["verified", "catch_all_valid"],
    "limit": 50
  }' | jq '.meta | {total, nextCursor}'

Remember one trap. As a filter, catch_all matches both catch_all and catch_all_valid. To get only the confirmed mailboxes, filter on catch_all_valid alone.

Also treat a search count of verified as an upper bound. Search carries no per-address proof of who verified it, and enrichment applies the strict rule. Confirm with Step 5 before a large send.

Step 4: Run the catch-all batch on its own

Pull catch_all separately and send it as a small second campaign. Mixing it into the main send hides the real bounce rate of both groups.

bash
curl -s -X POST "https://api.leadocean.io/v1/people/search" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jobLevel": ["VP"],
    "jobFunction": ["Sales & Business Development"],
    "country": ["CA"],
    "emailStatus": ["catch_all"],
    "limit": 25
  }' | jq '[.data[].contact_data.email_status] | group_by(.) | map({status: .[0], rows: length})'

Start with 50 to 100 addresses. If bounces stay low, send the rest. If they spike, stop. A catch-all verdict is a coin you cannot inspect, and the status is the only signal you have.

Step 5: Re-check one person before a big send

Enrichment returns the strict status for one person. Use one of linkedin_url, email, phone or person_id. A person we hold nothing for is a 404 and costs nothing.

bash
curl -s -X POST "https://api.leadocean.io/v1/people/enrich" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"person_id": "PERSON_ID_FROM_SEARCH", "reveal_email": true}' \
  | jq '.data.contact_data | {has_email, email_status, email_type}'

For the address alone, GET /v2/people/email/work returns one current-employer address or data: null. It only returns verified, catch_all_valid or catch_all, so null means none that qualify, not no email. Every v2 call costs one record, including a miss.

What you get

Counts from leadocean_count_leads on 2026-10-21, VPs in Sales & Business Development, country CA. Each is labelled by the filter used, so none is a mailable count.

code
emailStatus ["verified"]                      1,854  people  (1,392 companies)
emailStatus ["catch_all_valid","catch_all"]   3,720  people  (2,567 companies)
emailStatus ["risky","unknown"]               1,460  people  (1,147 companies)
credits: 0

The catch-all bucket is twice the verified bucket for this audience. That is why Step 4 matters.

Response shape from the API reference, placeholder values:

json
{
  "success": true,
  "data": {
    "contact_data": {
      "has_email": true,
      "email_status": "catch_all",
      "email_type": "work",
      "contact_emails": [
        { "email": "jane.doe@acme.com", "type": "work", "status": "catch_all", "verified_batch_date": "2026-09-20" }
      ]
    },
    "meta": { "source_ids": { "person_id": "<person_id>" } }
  },
  "meta": { "credits": 1 }
}

verified_batch_date set means LeadOcean ran the check. Null means it did not.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-key headerRe-copy the key from the app and check $LEADOCEAN_API_KEY is exported.
429Above the plan rate limit (5 requests a second on Free, 200 on Agency)Wait for Retry-After, then slow the loop.
402Records spent (1,000 for life on Free)Counts stay free. Move to a paid plan to keep pulling.
Empty resultsA misspelled enum value, such as Vp for VP or Verified for verifiedList valid values with leadocean_list_enum_values.
data: null from /v2/people/email/workNo address that is verified, catch_all_valid or catch_allTreat as no sendable work email.
Search stops with meta.depthCapped trueOne search pages to 10,000 rowsNarrow the filters and run smaller searches.

FAQ

Can I send to a catch-all email?

Yes, in a small separate batch. The domain accepts any name, so nobody can confirm the mailbox. Watch the bounce rate on that batch alone. A catch_all_valid address is safer, because the mailbox was confirmed.

Is a risky email safe to send?

Not by default. risky means the check ran and found a warning sign short of a clear no. Re-check it with enrichment, or leave it out of cold sends.

What is the difference between unknown and untested?

untested means nobody has checked the address. It is the largest group and may be fine. unknown means a check ran and was inconclusive, which is a weaker signal.

Does LeadOcean refund bounces?

No. Read email_status before you send. There is no refund or credit-back for bounced emails.

Where do I learn what a catch-all domain is?

Start with What Is a Catch-All Email?, then Catch-All Emails Explained. To compare tools, see Best Catch-All Email Verifiers. The free plan covers every call above. See pricing.

Sort your list by email status before you send. Start free.

Free to start. No credit card. 1,000 records to spend whenever you like.

Get your free API key →