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

How-to guide

How to Run B2B Cold Emailing (From Query to First Send)

Size the list for free, pull verified work emails, drop the risky ones, and send in small batches.

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

B2B cold emailing works when the list is small, the addresses are verified and the first message is short. Most failures start with the list, not the copy.

This guide covers the list half: query, count, pull, filter. It uses the LeadOcean API, so every step is a request you can run.

TL;DR

  • Size the audience for free, pull only verified and catch_all_valid work emails, and send in batches of 50 to 100 a day per mailbox.
  • Time: about 45 minutes for a 100-person test list. A new sending domain also needs warm-up first.
  • Records: 0 to count, 1 per search row and 1 per enrichment. The 100-person test list spends up to 200 of the 1,000 free records.

Prerequisites

  • A LeadOcean account and an API key on the Free plan, stored as $LEADOCEAN_API_KEY. No card needed.
  • curl and jq.
  • A sending domain with SPF, DKIM and DMARC set, and a mailbox that has been warmed (see inbox warming and inbox warm-up).
  • A sequencer or mailbox tool you already use. LeadOcean returns data. It does not send email.

Why list quality beats copy

A bad list caps every other lever. If 8% of addresses bounce, your domain reputation drops before anyone reads the subject line.

That is why this guide spends four steps on the list and two on the message. Verify first, write second.

Three rules cover most of it:

  • Send only to statuses you chose on purpose. Never load risky, unknown, role, disposable or spam_trap addresses.
  • Keep one audience per campaign, so one first line fits everyone on it.
  • Re-count before each new batch. Job changes move people out of a filter every month, since the dataset refreshes monthly.

Step 1: Define one narrow audience

Pick one title group, one company size and one country. A narrow list lets you write one specific first line.

Here is the audience used in this guide, written as filters. The values are exact strings, so check them first.

bash
curl -s "https://api.leadocean.io/v1/filters" \
  -H "x-api-key: $LEADOCEAN_API_KEY" | jq '.' | head -40
curl -s "https://api.leadocean.io/v1/enums" \
  -H "x-api-key: $LEADOCEAN_API_KEY" | jq '.' | head -40

Both calls are free. In the MCP server the same lookups are leadocean_list_filters and leadocean_list_enum_values.

The audience: VPs in Sales & Business Development, in the US, at companies with 51-200 employees.

Step 2: Count before you pull

Count the list first. A count costs no records and tells you if the filters are too tight or too loose.

bash
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": ["US"],
    "employeeRange": ["51-200"],
    "emailStatus": ["verified", "catch_all_valid", "catch_all"],
    "limit": 1
  }' | jq '.meta'

count=true goes in the query string with limit at 1, which makes the call free. The emailStatus line matches the default of the MCP count tool. Drop it to count everyone, with or without an address.

I ran the same filters through the free leadocean_count_leads tool on 2026-10-21. It returned 18,567 mailable people at 12,192 companies. The same filters with no email condition matched 60,456.

Mailable here means verified, catch_all_valid or catch_all, the tool's default. Sending to catch_all carries more bounce risk, so Step 3 narrows it.

Step 3: Pull people with a deliverable work email

Add emailStatus so you only receive addresses worth sending to. Keep verified and catch_all_valid for a first campaign.

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": ["US"],
    "employeeRange": ["51-200"],
    "emailStatus": ["verified", "catch_all_valid"],
    "limit": 100
  }' > page1.json
jq '.meta' page1.json

limit runs from 1 to 100. Each person returned costs one record, so this call spends up to 100.

To get the next page, send the cursor from the previous response. One search pages to 10,000 rows. Narrow the filters instead of walking further.

Step 4: Enrich the people you will write to

A search row is enough to build a list. Enrich only the people you plan to email, so you spend records on a short list.

Enrich takes exactly one of linkedin_url, email, phone or person_id. Use the person_id from search.

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 }'

reveal_email returns the address and costs no extra record. A person we hold nothing for is a 404 and costs nothing.

Since October 2026 enrich also returns career history in resume_data: past roles, education and certifications. That gives you a real opening line, such as a prior employer, instead of a template.

Step 5: Write the first message

Keep it under 90 words. One problem, one proof point, one low-effort question. Plain text, no images, one link at most.

text
Subject: {{first_line_topic}}

Hi {{first_name}},

{{one specific line from their role or past employer}}.

We help {{their team}} {{one outcome}}. {{one proof point you can verify}}.

Worth a short reply if this is on your list this quarter?

{{your_name}}

The {{...}} fields are placeholders for your sequencer's merge tags. Write the first line by hand for the first 50 people. Automate it only after you see what gets replies. For what to measure, see cold email response rate.

Step 6: Send small and watch the signals

Start at 20 to 30 emails a day per mailbox and raise it over two weeks. Spread sends across mailboxes and across the day. Send in the recipient's working hours, Tuesday to Thursday if you have no data of your own yet.

Use this checklist as the stop rule.

yaml
before_send:
  - email_status in [verified, catch_all_valid]
  - SPF, DKIM, DMARC pass
  - mailbox warmed (no cold start)
  - unsubscribe line in every message
pause_if:
  - bounce_rate > 2%
  - any spam complaint
  - open and reply rate fall week over week

LeadOcean gives no refund for bounced emails, so read email_status before you send. Plan limits and rates are on /pricing.

Step 7: Plan follow-ups and suppress as you go

Plan three emails over ten days. Each one adds a new fact, not a reminder.

Keep a suppression file of everyone who replied, bounced or opted out. Check every new pull against it before you load a sequencer.

text
Day 1   First message (the template above)
Day 4   Follow-up: one new proof point, one sentence
Day 10  Last note: a clear opt-out, then stop

Dedupe on the email address, not the name. The same person can appear at two companies after a job change, so check the current role in contact_current_experiences[] from enrich before you send.

Never email the same person from two campaigns in one week. Two senders from one company to one buyer reads as spam, even when each message is fine.

What you get

Response shape from the API reference for the Step 2 count. The count call returns metadata and an empty data array.

json
{
  "success": true,
  "data": [],
  "meta": {
    "total": 18567,
    "totalIsExact": true,
    "distinctCompanies": 12192,
    "countOnly": true,
    "credits": 0
  }
}

The total and distinctCompanies above are the 2026-10-21 figures from leadocean_count_leads with the same filters. The REST count caps meta.total at 100,000.

A pull returns up to 100 person rows in data and a nextCursor for the next page. Exported CSV headers come back as email_1_address, email_1_status and phone_1_number.

Cost of this run: 0 records for the count, 100 for the pull, up to 100 for enrichment if you enrich the whole page. That is 200 of the 1,000 free records at most.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-key headerCheck $LEADOCEAN_API_KEY is set and the header name is exact
429Above the plan's rate limit (5 requests a second on Free, 200 on Agency)Wait for the Retry-After value, or slow the loop
402The 1,000 free records are spentMove to Solo ($97 a month) or Agency ($497 a month)
Zero resultsA filter value is not an exact string, such as jobLevel: "vp"Look up values with /v1/enums and use VP
Count is exactly 100,000meta.total is capped at 100,000Add filters until the total drops below the cap
Export refusedOver 50,000 rows a request or 500,000 a daySplit the export into smaller filter slices

FAQ

How many emails should I send a day?

Start with 20 to 30 a day per mailbox and build up over two weeks. Add mailboxes before you add volume to one.

Should I send to catch_all addresses?

Not in the first campaign. A catch_all server accepts any address, so a bounce can still happen. Start with verified and catch_all_valid.

Does LeadOcean send the emails?

No. It returns data through the API, the MCP server and an Exports page in the app. You send with your own sequencer.

How many records does a test list cost?

A 100-person pull spends 100 records, and enriching all 100 spends 100 more. Counting is free, and an enrichment that finds nothing is free. The Free plan has 1,000 records for life.

Do I need a paid plan?

Not for a test. Bulk CSV export is not on Free, so move to Solo at $97 a month once you need lists of thousands.

Build your first B2B cold email list on real, verified data

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

Get your free API key →