Cold email lands in the inbox when four things hold: the domain is authenticated, the list is clean, volume ramps slowly and spam complaints stay near zero. This guide covers each in order, with commands.
TL;DR
- Publish SPF, DKIM and DMARC on every sending domain, then filter your list to checked addresses before the first send. Setup takes about 90 minutes.
- Sizing a list with the LeadOcean count tool spends 0 records. A 100-row test batch spends 100, inside the Free plan's 1,000.
- Keep the spam rate in Google Postmaster Tools under 0.10% and never let it reach 0.30% (Google sender guidelines, October 2026).
Prerequisites
- A secondary sending domain, such as getacme.com (a placeholder), and at least one mailbox on it.
- Access to the domain's DNS settings.
- A free Google Postmaster Tools account for the domain.
- A LeadOcean API key in
$LEADOCEAN_API_KEY. The Free plan has 1,000 records for life and no card. digandcurlon your machine.
Step 1: Publish SPF, DKIM and DMARC
Authentication comes first. Google asks all senders for SPF or DKIM, and bulk senders (5,000 or more messages a day) for SPF, DKIM and DMARC (Google, October 2026). Set all three on every domain anyway.
The standards are SPF (RFC 7208), DKIM (RFC 6376) and DMARC (RFC 7489). Example records for a Google Workspace mailbox, with placeholder values:
getacme.com. TXT "v=spf1 include:_spf.google.com ~all"
google._domainkey.getacme.com. TXT "v=DKIM1; k=rsa; p=<public key from your mail host>"
_dmarc.getacme.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc@getacme.com"Start DMARC at p=none and read the reports for a week. Google says DKIM keys must be 1024 bits or longer for personal Gmail, and recommends 2048 (Google, October 2026).
Check that the records resolve:
dig +short TXT getacme.com
dig +short TXT google._domainkey.getacme.com
dig +short TXT _dmarc.getacme.comAll three should print a record. An empty answer means the record is missing or not yet propagated.
Step 2: Add an unsubscribe route
Every message needs an easy way to opt out. Above 5,000 messages a day, Google requires one-click unsubscribe on marketing and subscribed messages, with two headers (Google, October 2026):
List-Unsubscribe-Post: List-Unsubscribe=One-Click
List-Unsubscribe: <https://getacme.com/unsubscribe/abc123>Most sending tools set these for you. Check one test message's raw headers to be sure.
Step 3: Size the list by email status
List quality decides your bounce rate, and bounces hurt a new domain fastest. LeadOcean tags every address with an email_status. A count that returns verified only is the strictest cut.
Size it for free. count=true is a query parameter, and limit=1 keeps the call free:
curl -s "https://api.leadocean.io/v1/people/search?count=true&limit=1" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jobLevel": ["VP"],
"jobFunction": ["Sales & Business Development"],
"country": ["CA"],
"emailStatus": ["verified"]
}'The response carries meta.total (capped at 100,000) and meta.countOnly: true, and spends nothing. If an enum value is wrong you get zero rows, so resolve values first with GET /v1/enums or the leadocean_list_enum_values MCP tool. Enum strings are exact: VP, not vp.
One caveat. Search counts of verified are an upper bound. Enrichment applies the narrow rule per address, so confirm per address before a large send (LeadOcean email_status docs, October 2026).
Step 4: Pull a test batch and read the status
Pull 100 rows first, not 10,000. A small batch tells you the real bounce rate before it can damage the domain.
curl -s "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"],
"limit": 100
}'Each person returned costs one record. Page with cursor from meta. For lists over 100 rows use POST /v1/exports (up to 50,000 rows a request, 500,000 a day). Bulk CSV export is not on Free.
Skip any row whose status is risky, role, derived, invalid, disposable, spam_trap or abuse. LeadOcean does not refund or credit bounces, so read email_status before sending.
Step 5: Ramp volume and watch spam rate
Start each new mailbox at a low daily cap and raise it in small steps over two to four weeks. The cap is your own choice, not a standard. Our illustration starts at 10 to 20 a day per mailbox.
Check Postmaster Tools every day during the ramp. Google's numbers: keep spam rate below 0.10% and avoid ever reaching 0.30% (Google, October 2026). If you cross 0.10%, pause the domain and cut the list to verified only.
Once reports show clean SPF and DKIM alignment, move DMARC from p=none to p=quarantine.
What you get
A free count run on 2026-10-21 with the LeadOcean MCP tool leadocean_count_leads, filters jobLevel VP, jobFunction Sales & Business Development, country CA:
| emailStatus filter | People | Distinct companies |
|---|---|---|
verified only | 1,854 | 1,392 |
| default (verified, catch_all_valid, catch_all) | 5,574 | 3,873 |
| no email condition | 18,800 | not reported |
Both counts spent 0 records. The strict verified cut is about a third of the default list. That gap is the trade between volume and bounce risk.
A record row from search looks like this. Response shape from the API reference, placeholder values:
{
"success": true,
"data": [
{
"profile_data": {
"profile_full_name": "Jane Doe",
"profile_headline": "VP Sales at Acme"
},
"contact_data": {
"has_email": true,
"email_status": "verified",
"email_type": "work",
"contact_current_experiences": [
{ "job_title": "VP Sales", "company_name": "Acme", "company_domain": "acme.com" }
]
},
"meta": { "source_ids": { "person_id": "1234567" }, "fetched_at": "2026-10-01" }
}
],
"meta": { "count": 1, "limit": 100, "credits": 1, "nextCursor": "..." }
}Search rows carry the status flag, not the address. Enrich by person_id with reveal_email to get the address.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Send the key in x-api-key and check $LEADOCEAN_API_KEY is set in the shell. |
| 429 | Above the plan's rate limit (5 a second on Free, 200 on Agency) | Honor Retry-After, then slow the loop. |
| 402 | Records spent | Free is 1,000 records for life. Move to a paid plan on the pricing page. |
| Zero rows | Enum value spelled wrong, such as vp for VP | Call GET /v1/enums or leadocean_list_enum_values and copy the exact string. |
| Export refused | Over 50,000 rows a request or 500,000 a day, or Free plan | Split the request, or use Solo or higher for bulk CSV. |
| Spam rate climbing | Dirty list, no unsubscribe, or too much volume too soon | Pause, filter to verified, and cut the daily cap. |
FAQ
How many emails a day is safe on a new mailbox?
There is no standard number. Start low, such as 10 to 20 a day per mailbox, and raise it only while bounces and spam rate stay flat. Add mailboxes for more total volume instead of pushing one past its cap.
Should I send to catch-all addresses?
A catch-all domain accepts any address, so a check cannot confirm the mailbox. LeadOcean's catch_all_valid status means the mailbox was confirmed through the provider's identity check. For a new domain, start with verified and add catch_all_valid once the domain has history.
Do I need DMARC for cold email?
Google requires it for senders above 5,000 messages a day. Below that, publish it anyway. It costs one DNS record and the reports show who else sends as your domain (RFC 7489).
Does a verified list fix poor deliverability?
No. It removes bounces, which is one cause. Authentication, volume and complaint rate are separate causes. See sending infrastructure 101 for the layers and how to improve email deliverability for fixes. The checklist in email deliverability best practices for B2B covers the rest. More guides are on the blog.
Size a checked list before you send
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →