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

How-to guide

How to Verify Emails for Ecommerce Outreach (and Protect Your Sender Reputation)

Size an ecommerce audience by email status for free, spot-check three addresses, export only the safe ones and keep bounces out of your domain.

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

TL;DR

  • Read the email status before you send, and send only to verified and catch_all_valid first. LeadOcean stores a status on every address and lets you filter on it for free.
  • It takes about 20 minutes. Sizing and filter lookups cost nothing. The spot-check spends 6 records; the 250-row export spends up to 250 more and needs Solo ($97 a month) or above, since bulk CSV export is not on Free.
  • This guide covers B2B contacts at ecommerce brands. It is not a bulk checker for your own storefront's customer list (see the FAQ).

Bounces and spam complaints are what damage a sending domain. Mailbox providers watch both. Google tells bulk senders to keep the spam rate in Postmaster Tools below 0.10% and never reach 0.30% (Google email sender guidelines, September 2026). A list full of dead addresses is the fastest way to a bad first week.

Prerequisites

  • A LeadOcean account. Start free: no card, 1,000 records, same API and data as the paid plans.
  • An API key in your shell: export LEADOCEAN_API_KEY=... (the key comes from app.leadocean.io).
  • curl, jq and Python 3.
  • A sending domain you want to protect, and a place to read its bounces (your sequencer or Postmaster Tools).

Step 1: Size the audience by email status

Count first. A search with count=true as a query parameter and limit 1 spends no records. The filters go in a flat JSON body.

bash
BODY='{"technologyCategories":["Ecommerce"],"jobFunction":["Advertising & Marketing"],"jobLevel":["Director","VP","C-Team"],"country":["US"]'

#1 verified only
curl -s -X POST "https://api.leadocean.io/v1/people/search?count=true&limit=1" \
  -H "x-api-key: $LEADOCEAN_API_KEY" -H "Content-Type: application/json" \
  -d "$BODY"',"emailStatus":["verified"]}' | jq '.meta | {total, totalIsExact}'

#2 verified + catch-all (the default mailable set)
curl -s -X POST "https://api.leadocean.io/v1/people/search?count=true&limit=1" \
  -H "x-api-key: $LEADOCEAN_API_KEY" -H "Content-Type: application/json" \
  -d "$BODY"',"emailStatus":["verified","catch_all_valid","catch_all"]}' | jq '.meta | {total, totalIsExact}'

#3 everything else (risky, role, untested and so on)
curl -s -X POST "https://api.leadocean.io/v1/people/search?count=true&limit=1" \
  -H "x-api-key: $LEADOCEAN_API_KEY" -H "Content-Type: application/json" \
  -d "$BODY"',"emailStatus":["risky","unknown","untested","invalid","role","disposable","spam_trap","abuse","derived","none"]}' | jq '.meta | {total, totalIsExact}'

technologyCategories: ["Ecommerce"] selects people at companies whose website shows an ecommerce platform. It covers the about 7.8M sites we crawled, so it is not every online shop.

Step 2: Write a send policy

Decide in advance what each status does. Save it as policy.json. Your export script reads it, so nobody sends to risky on a Friday afternoon.

json
{
  "send_now":  ["verified", "catch_all_valid"],
  "send_slow": ["catch_all"],
  "hold":      ["risky", "role", "derived", "untested", "unknown"],
  "never":     ["invalid", "spam_trap", "abuse", "disposable", "none"]
}

Why these buckets:

  • verified means our own verifier sent to the address and it accepted. catch_all_valid is a mailbox we confirmed on a catch-all domain.
  • catch_all domains accept any address, so the mailbox cannot be confirmed. Send a small batch last and watch it.
  • role (info@, sales@) and derived (built from the company's address pattern, never tested) are risks, not errors. untested means nobody has checked the address yet.

Read the full definitions with GET /v1/enums/email_status.

Step 3: Spot-check three addresses

A search row carries flags, not addresses. Pull three people from the safe set, then enrich them by person_id to read the per-address verdict. Search status is an upper bound, and enrichment gives the correct answer.

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 '{"technologyCategories":["Ecommerce"],"jobFunction":["Advertising & Marketing"],"jobLevel":["VP"],"country":["US"],"emailStatus":["verified"],"emailType":["work"],"limit":3}' \
  | jq -r '.data[].meta.source_ids.person_id' > ids.txt

while read -r id; do
  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\":\"$id\",\"reveal_email\":true}" \
  | jq '.data.contact_data.contact_emails[0] | {status, type, verified_batch_date}'
done < ids.txt

This spends 6 records at most: 3 for the search rows and 3 for the enrichments. Check verified_batch_date. A date means our verifier ran the check. null means the status came from a source we hold to the same standard, and it does not mean unverified.

Step 4: Export the safe segment and filter it again

Export with the status columns so the CSV carries the proof. Bulk CSV export starts on Solo ($97 a month); on Free, page through search and enrich instead. Each row costs one record. The export reserves up to limit records up front (twice that when a column needs the full record) and gives back what it does not use.

bash
curl -s -X POST "https://api.leadocean.io/v1/exports" \
  -H "x-api-key: $LEADOCEAN_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "name": "ecom-marketing-leaders-safe",
    "limit": 250,
    "filters": {
      "technologyCategories": ["Ecommerce"],
      "jobFunction": ["Advertising & Marketing"],
      "jobLevel": ["Director","VP","C-Team"],
      "country": ["US"],
      "emailStatus": ["verified","catch_all_valid"],
      "emailType": ["work"]
    },
    "columns": ["profile_first_name","current_company_domain","current_job_title",
                "email_1_address","email_1_type","email_1_status","email_1_verified_date"],
    "caps": {"emails": 1}
  }'

Poll GET /v1/exports/{id} until status is done, then download from GET /v1/exports/{id}/download. Last, apply the policy file to the CSV, because a filter on search can still let a row through that enrichment would reject.

python
import csv, json

policy = json.load(open("policy.json"))
ok = set(policy["send_now"])

with open("export.csv") as src, open("send_now.csv", "w", newline="") as out:
    rows = csv.DictReader(src)
    w = csv.DictWriter(out, fieldnames=rows.fieldnames)
    w.writeheader()
    kept = dropped = 0
    for r in rows:
        if r["email_1_status"] in ok and r["email_1_type"] == "work":
            w.writerow(r); kept += 1
        else:
            dropped += 1
print(f"kept {kept}, dropped {dropped}")

You can also build the same export in the app: Exports page, filter builder, column picker and a price preview before you start.

Step 5: Suppress bounces and watch the spam rate

Verification lowers bounces. It does not remove them, and it says nothing about whether the reader wants your email. Put every hard bounce on a suppression list and check it before each send.

python
import csv

suppressed = {line.strip().lower() for line in open("suppressed.txt")}

def safe_to_send(email: str) -> bool:
    return email.lower() not in suppressed

with open("suppressed.txt", "a") as f:  # run after each campaign
    for row in csv.DictReader(open("bounces.csv")):
        if row["type"] == "hard":
            f.write(row["email"].lower() + "\n")

Then watch the domain in Postmaster Tools. If the spam rate climbs toward the 0.10% line Google names, stop and cut volume before you add more.

LeadOcean does not refund or credit back bounced emails. Read email_status before you send, not after.

What you get

Free counts from the LeadOcean MCP server on 2026-10-21 (leadocean_count_leads, which spends no records), for US marketing Directors, VPs and C-Team at companies whose site shows an ecommerce platform:

text
filter                                                             total
default mailable (verified, catch_all_valid, catch_all)            8,390
emailStatus: verified                                              2,711
emailStatus: risky, unknown, untested, invalid, role, disposable,
             spam_trap, abuse, derived, none                      13,654

Read it as three facts:

  • Only about 38% of these people (8,390 of 22,044) sit in the default mailable set.
  • Roughly one in eight (2,711) is verified. Treat that as an upper bound until you enrich.
  • The largest group is the one you never want to mail blind. Verification is a filter, not a cleanup after the fact.

Response shape from the API reference for one enriched person, with placeholder values:

json
{
  "success": true,
  "data": {
    "profile_data": { "full_name": "Jane Doe" },
    "contact_data": {
      "has_email": true,
      "email_status": "verified",
      "email_type": "work",
      "contact_emails": [
        {
          "email": "jane.doe@acme.com",
          "type": "work",
          "status": "verified",
          "verified_batch_date": "2026-09-20"
        }
      ]
    }
  }
}

Troubleshooting

ErrorCauseFix
401Missing, wrong or revoked keyRe-copy the key from app.leadocean.io and re-export $LEADOCEAN_API_KEY.
429Above your plan's rate limit per account (5 a second on Free, 50 on Solo, 200 on Agency), or an export past its daily ceiling (500,000 rows a day)Wait for Retry-After and retry. Send full pages of 100 instead of more calls.
402Free plan: the 1,000 records are spentCheck what is left with GET /v1/account, or move to Solo ($97 a month) or Agency ($497 a month). Free records do not reset.
400, or a count of 0A filter value outside the catalogue, such as jobFunction: "Marketing"Resolve it with leadocean_list_enum_values or GET /v1/enums/<name> and pass the exact string.
400 export_limit_rowslimit is above 50,000 rows for one exportSplit the audience by country or headcount band and run several exports.
404 on enrichWe hold nothing for that personNothing to fix and nothing charged. Skip the row.

FAQ

Can I use LeadOcean to verify my own storefront customer list?

No. LeadOcean verifies the B2B contacts in its own dataset. It does not run a bulk check on a list you upload. We do not verify consumer domains at all, so a personal address marked verified was asserted by a supplier. For a customer list, use double opt-in and a dedicated checker. See best email verification tools and the super email verifier.

Should I send to catch-all addresses?

Only after verified and catch_all_valid, and in a small batch. A catch-all domain accepts every address, so the mailbox cannot be confirmed. catch_all_valid is the exception: we confirmed that mailbox exists.

How current are the statuses?

The dataset is refreshed monthly, and each record carries its own fetched_at date. verified_batch_date on an address names the verification run that produced its status. Re-check before a large send if the list has sat for weeks.

What does LeadOcean cost for this?

Free is 1,000 records, one-off, with no card. Solo is $97 a month for one person and Agency is $497 a month for teams; paid plans have a fair-use pace, not credits. Counts and filter lookups are free on every plan. See pricing.

Where do I learn what each status means?

Start with email verification explained. More recipes live in the blog hub.

Verify your first ecommerce list before it touches your domain

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

Get your free API key →