Cold email marketing tools fall into four layers: data, verification, sending and infrastructure. Pick one tool per layer and keep the layers separate, so you can swap one without rebuilding the rest.
Most guides list sender apps and stop. The layer that decides your reply rate is the list, so this page builds that part for real.
TL;DR
- Four layers: data, verification, sending, infrastructure. Buy each on its own merits.
- The recipe takes about 20 minutes and spends 100 LeadOcean records. Sizing and polling are free.
- Gate every list on the
email_1_statuscolumn before it touches a sender. Bounces are not refunded.
Prerequisites
- A free LeadOcean account at app.leadocean.io. The free plan has 1,000 records, one-off, no card.
- An API key from the app, exported as
$LEADOCEAN_API_KEY. curland Python 3.- A sender account to import into. Any sender that takes a CSV works.
export LEADOCEAN_API_KEY="paste-your-key-here"Step 1: Split the stack into four layers
Write down one tool for each layer before you compare features. This is the whole decision.
| Layer | Job | What to check |
|---|---|---|
| Data | Who to email, with a work address and a status | Filters, status per address, price per record |
| Verification | Drop addresses that will bounce | Does the data layer already return a status? |
| Sending | Sequences, replies, scheduling | Per-mailbox limits, reply handling, API |
| Infrastructure | Domains, mailboxes, warm-up, DNS | Separate sending domain, SPF, DKIM, DMARC |
How to decide inside each layer:
- Data: buy on filters and status, not on a headline database size. A list you cannot size for free is a list you cannot test.
- Verification: if the data layer returns a status per address, a second verifier adds a step, not much safety. Add one only for lists you import from elsewhere.
- Sending: pick for reply handling and mailbox limits. Sequence builders matter less than whether you can pause one mailbox when its bounce rate climbs.
- Infrastructure: buy separate sending domains and warm them up before the first campaign. Set SPF, DKIM and DMARC on each.
Two rules. Never send from your primary domain. And pick a sender that imports a CSV or has an API, because you will re-run the list every month.
LeadOcean is the data layer, and it returns an email status on every address, which covers most of the verification layer. It is not a sender: no sequences, dialer or CRM. For a worked sender import, see the ReachHQ recipe.
Step 2: Write the audience as filters
Describe the audience as one flat JSON object. Filters are arrays of strings, and enum values are exact strings.
This example is VP-level sales people at 51 to 200 person companies in the US, with a verified work email.
{
"jobFunction": ["Sales & Business Development"],
"jobLevel": ["VP"],
"country": ["US"],
"employeeRange": ["51-200"],
"emailType": ["work"],
"emailStatus": ["verified"]
}Save it as audience.json. The same keys work in people search, in the export filters object and in the MCP tools.
Do not guess enum values. VP works, vp does not. Check strings with GET /v1/enums or the free leadocean_list_enum_values MCP tool.
Step 3: Size the list before you spend anything
Send the filters to people search with count=true as a query parameter and limit 1. It returns the total and no rows, so it costs no records.
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 '{
"jobFunction": ["Sales & Business Development"],
"jobLevel": ["VP"],
"country": ["US"],
"employeeRange": ["51-200"],
"emailType": ["work"],
"emailStatus": ["verified"],
"limit": 1
}'Read meta.total, which is capped at 100,000. A people count also carries meta.distinctCompanies, the number of employers the list spans. That number matters for cold email: 4,000 people at 3,000 companies is a better list than 4,000 people at 400.
Size three or four variants. Each call is free.
Step 4: Export a small test batch
POST /v1/exports takes filters, limit, columns and caps. Numbered columns are requested as email_N_address, and caps sets how many come back. Start with 100 rows to test your sender import.
curl -s -X POST https://api.leadocean.io/v1/exports \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "us-vp-sales-test",
"limit": 100,
"filters": {
"jobFunction": ["Sales & Business Development"],
"jobLevel": ["VP"],
"country": ["US"],
"employeeRange": ["51-200"],
"emailType": ["work"],
"emailStatus": ["verified"]
},
"columns": [
"profile_full_name",
"current_job_title",
"current_company_name",
"current_company_domain",
"email_N_address",
"email_N_status"
],
"caps": { "emails": 1 }
}'One export holds up to 50,000 rows, and the account ceiling is 500,000 rows a day. Bulk CSV export is not on the Free plan, so the 100-row test needs a paid plan. Solo at $97 a month includes bulk export (see pricing). The Exports page in the app does the same job with a filter builder and the record price before you start.
Poll GET /v1/exports/{id} until status is done, then download.
curl -s -H "x-api-key: $LEADOCEAN_API_KEY" \
"https://api.leadocean.io/v1/exports/$EXPORT_ID/download" -o test.csvStep 5: Gate the file, then import it
Keep only rows you would send to, and drop duplicate addresses. Do this on every export.
import csv
SEND_OK = {"verified", "catch_all_valid"}
seen, rows = set(), []
with open("test.csv", newline="") as f:
for r in csv.DictReader(f):
addr = (r["email_1_address"] or "").strip().lower()
if addr and r["email_1_status"] in SEND_OK and addr not in seen:
seen.add(addr)
rows.append(r)
with open("send-list.csv", "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=rows[0].keys())
w.writeheader()
w.writerows(rows)
print(len(rows), "rows ready")Import send-list.csv into your sender. Map email, first name, company and title. Tag the segment name so you can compare replies by audience. For what to measure after, read cold email response rate and the cold email statistics.
Keep the filter JSON in version control. It is the real asset: it defines your audience, and you can re-run it when the monthly refresh lands. A list file is a snapshot. A filter set is repeatable.
Send the 100-row batch first. If bounces stay low, run the full list. A bigger guide to the list itself is in email marketing databases.
Step 6: Re-run the list every month
A cold email list decays. People change jobs, and domains stop accepting mail. The dataset is refreshed monthly, so re-run the same filters before each large send instead of reusing last quarter's file.
Use this routine:
- Size the filters again. A count that drops sharply means a filter value changed or the segment shrank.
- Export only the segment you will send this month, and name it with the date.
- Suppress anyone who replied, unsubscribed or bounced last time. Your sender holds that list, not the data layer.
- Gate on
email_1_statusagain with the script from Step 5.
Treat deliverability as a loop, not a setup task. Watch the bounce rate on the first 100 sends of every new batch. If it climbs, stop and check the status mix in the file before you blame the copy.
What you get
Sizing is free, so here are two real counts from the LeadOcean MCP server on 2026-10-21. Both are US VP-level sales people at 51 to 200 person companies.
| Filters | Count | Employers | Date |
|---|---|---|---|
emailType work, emailStatus verified | 4,027 | 3,052 | 2026-10-21 |
| Default mailable (verified, catch_all_valid or catch_all), any email type | 18,567 | 12,192 | 2026-10-21 |
The same filters with no email condition match 60,456 people. The two rows use different email filters, so do not subtract one from the other.
The count call returns this shape. This is the response shape from the API reference, with placeholder values.
{
"success": true,
"data": [],
"meta": {
"total": 4027,
"totalIsExact": true,
"countOnly": true,
"credits": 0
}
}The downloaded CSV has the columns you asked for, with numbered columns expanded to email_1_address and so on. One placeholder row:
profile_full_name,current_job_title,current_company_name,current_company_domain,email_1_address,email_1_status
Jane Doe,VP Sales,Acme,acme.com,jane.doe@acme.com,verifiedTroubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Copy the key from the app again. Check $LEADOCEAN_API_KEY is set in this shell. |
| 429 | Above the plan's rate limit (5 a second on Free, 200 on Agency) | Slow the loop. Poll an export at the pollAfter interval. |
| 402 | Out of records, or fewer left than the export needs | Lower limit. Records are reserved for the whole export before it starts. |
| Empty results | A filter value that is not an exact enum string | Pull strings from GET /v1/enums or leadocean_list_enum_values. VP, not vp. |
400 export_limit_rows | limit is above 50,000 rows for one export | Split the audience by country or level and run several exports. |
429 export_limit_daily | You passed the 500,000 rows a day ceiling | Wait for resetsAt in the response, then run the next export. |
FAQ
What are cold email marketing tools?
They are the software behind outbound email: a data source for contacts, a verifier, a sender with sequences and reply handling, and sending infrastructure such as domains and mailboxes. Some products bundle two layers. Buy the weakest layer first.
Do I need a separate email verifier?
Not if your data source returns a status per address. Read email_1_status and send only verified and catch_all_valid. A separate verifier still helps if you import lists from elsewhere.
What happens when an address bounces?
Nothing is refunded. There is no credit-back for bounced emails, so gate on status before you send. LeadOcean is the data, not the sender.
How fresh is the data?
The dataset is refreshed monthly, and each record carries its own fetched_at date. Re-export a segment before a large send.
How much does the data layer cost?
Build the data layer of your cold email stack. Size it free, export when it looks right.
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →