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
verifiedandcatch_all_validwork 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. curlandjq.- 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,disposableorspam_trapaddresses. - 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.
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 -40Both 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.
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.
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.jsonlimit 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.
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.
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.
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 weekLeadOcean 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.
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 stopDedupe 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.
{
"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
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Check $LEADOCEAN_API_KEY is set and the header name is exact |
| 429 | Above the plan's rate limit (5 requests a second on Free, 200 on Agency) | Wait for the Retry-After value, or slow the loop |
| 402 | The 1,000 free records are spent | Move to Solo ($97 a month) or Agency ($497 a month) |
| Zero results | A filter value is not an exact string, such as jobLevel: "vp" | Look up values with /v1/enums and use VP |
| Count is exactly 100,000 | meta.total is capped at 100,000 | Add filters until the total drops below the cap |
| Export refused | Over 50,000 rows a request or 500,000 a day | Split 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 →