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

How-to guide

How to Automate Cold Email with Claude Code (Personalize at Scale)

Size the list for free, pull verified contacts, draft one line per lead, and hand a CSV to your sender. About 30 minutes.

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

Claude Code can run the data half of cold email: size a list, pull verified contacts, and draft a first line for each. It cannot send. This guide is the short version, with the queries and the output.

The split is deliberate. Claude reads a record and writes one sentence about it well. It cannot know who exists. A database answers that with a count you can check, so never ask Claude to find leads from memory.

TL;DR

  • Claude Code connects to the LeadOcean MCP server in one command, so it can count, search and enrich leads without you pasting a key.
  • The whole build takes about 30 minutes. Sizing the list is free. A 25-lead test run spends about 50 records of your 1,000 free ones.
  • Claude writes the first line and the draft. You review, then load the CSV into the sender you already use.

Prerequisites

  • A LeadOcean account. Start free: no card, 1,000 records for life, 5 requests a second.
  • Claude Code installed and signed in.
  • An API key from app.leadocean.io, exported as $LEADOCEAN_API_KEY, for the REST steps.
  • A sender that takes a CSV (any sequencer). LeadOcean has no sequencer, so it does not send email.

Step 1: Connect the MCP server

Run one command in your terminal. Claude Code documents claude mcp add --transport http <name> <url> for remote servers (Claude Code docs, October 2026).

bash
claude mcp add --transport http leadocean https://api.leadocean.io/mcp

Then open Claude Code and run /mcp. Pick leadocean and sign in. Auth is OAuth 2.1, so you paste no key.

The server exposes 13 tools. Seven are free, including leadocean_count_leads, leadocean_count_companies, leadocean_list_filters and leadocean_list_enum_values. Search, get, phone and export tools spend records.

Step 2: Size the list before you spend anything

Ask for a count first. A count costs 0 records.

text
Use leadocean_count_leads. Filters: jobLevel VP, jobFunction
"Sales & Business Development", country US. Report the total,
distinctCompanies and the top 5 industries. Spend no records.

Enum values are exact strings. If a filter returns nothing, ask Claude to call leadocean_list_enum_values for that field before you retry.

The default count covers only addresses worth sending to: verified, catch_all_valid and catch_all. Say so when you quote the number.

Step 3: Tighten the list before you scale

Add filters until the count looks like a list you would hand-pick. Ask Claude to try three variants and report each total.

text
Call leadocean_count_leads three times with jobLevel VP, country US:
1) add employeeRange 51-200
2) add industry "Software Development"
3) add technologies ["HubSpot"]
Show the three totals side by side. Spend no records.

Do not count with funding, revenue, HQ city or HQ state filters. The count tool rejects them or returns a fake total, so apply those at search time and say the list is narrower.

Pick the variant with a few thousand people, not a few hundred thousand. You will review a sample of every send, and a smaller list keeps that honest.

Step 4: Pull a small test batch

Search 25 people, not 25,000. You are testing the prompt, not the list.

bash
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": ["US"],
    "industry": ["Software Development"],
    "emailStatus": ["verified"],
    "limit": 25
  }' > batch.json

Search returns a thin projection with no contact points. Each person returned is one record. Page with meta.nextCursor.

You can ask Claude Code to do the same through the MCP tool instead. The REST call is better once you script it, because the file lands on disk and Claude can read it.

Step 5: Enrich the people you will write to

Enrich returns the email (with reveal_email) and the profile detail you need for personalization. Take one input per call: person_id, linkedin_url, email or phone.

bash
curl -s 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}'

Since October 2026, enrich also returns resume_data: past roles, education, certifications and awards when held. That gives Claude real material for a first line. A person we hold nothing for is a 404 and costs nothing.

Stay under 5 requests a second on Free. A short loop with a 250 ms sleep is enough.

Step 6: Write the personalization prompt

Put the rules in a file Claude Code reads every time. Save this as CLAUDE.md in the project folder.

markdown
Cold email rules (project instructions)
- Input: enriched/*.json. Output: drafts.csv with columns
  email, first_name, first_line, body.
- first_line: one sentence, under 20 words, built from ONE fact in
  the record (current title, a past role, a certification).
- If the record has no usable fact, leave first_line empty. Never guess.
- body: 3 sentences. Problem, proof, one question. No links.
- Skip any row whose email_status is not verified.
- Never invent a customer, a metric or a mutual connection.

Run it with a short instruction.

text
Read enriched/*.json and apply the cold email rules. Write drafts.csv.
Then print 3 rows so I can check the first lines.

The empty-line rule matters. A blank first line sends a plain email. A made-up one sends a lie.

Step 7: Check the drafts before you send

Run three checks on every batch. They take five minutes.

bash
jq -r 'select(.data.contact_data.email_status != "verified") | .data.profile_data.profile_full_name' enriched/*.json
python3 -c "import csv; r=list(csv.DictReader(open('drafts.csv'))); print(sum(not x['first_line'] for x in r), 'of', len(r), 'first lines empty')"

The first line lists people whose status is not verified. None of them should appear in drafts.csv.

  • Open 10 random rows and check the first-line fact against the record.
  • Count empty first lines. A high share means the filter is thin on profile data, not that the prompt is wrong.
  • Confirm every row has email_status of verified. Drop the rest.

Version the rules file. When a first line goes wrong, add one rule that prevents it.

Step 8: Review, then hand off

Read every first line in the 25-row batch. Fix the prompt, not the rows, so the next 1,000 improve too.

Then export for the sender. Bulk CSV export is included on Solo and above. Free has no bulk export, so for a test batch use drafts.csv from Step 6. The export body takes filters (the people search filters), limit and an optional preset or columns list (API reference, October 2026).

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": "us-vp-sales-verified",
    "filters": {"jobLevel": ["VP"], "jobFunction": ["Sales & Business Development"], "country": ["US"], "emailStatus": ["verified"]},
    "limit": 1000,
    "preset": "full"
  }'

Poll GET /v1/exports/{id} until it is done, then download the file. An export takes up to 50,000 rows a request and 500,000 a day, and every row costs one record. Read email_1_status in the CSV before you send: there is no refund for bounces.

A 1,000-lead run is about 2,000 records if you search and then enrich: 1,000 from search and up to 1,000 from enrichment. An export with the full preset returns the contact columns in one pass at one record a row. Either way it is past the 1,000 Free records. Solo at $97 a month covers one person at this volume. Agency at $497 a month is for teams, with 5 keys. Paid plans have a fair-use pace, not credits, so there is no overage invoice. See pricing.

What you get

Here is the free count from Step 2, run on 21 October 2026 through leadocean_count_leads. It cost 0 records.

json
{
  "total": 129494,
  "totalIsExact": true,
  "totalAll": 350517,
  "distinctCompanies": 58121,
  "distinctCompaniesIsExact": false,
  "credits": 0,
  "facets": {
    "industry": [
      {"value": "Financial Services", "count": 12677},
      {"value": "Software Development", "count": 10144},
      {"value": "Banking", "count": 7813}
    ]
  }
}

Read it like this. 129,494 US VPs in sales and business development have an address worth sending to. 350,517 match with no email filter. They sit across about 58,121 employers (an estimate above 40,000).

A search page looks like this. Response shape from the API reference, placeholder values only:

json
{
  "success": true,
  "data": [
    {
      "profile_data": {"profile_full_name": "Jane Doe", "profile_headline": "VP Sales at Acme"},
      "contact_data": {
        "contact_current_experiences": [{"job_title": "VP Sales", "company_name": "Acme", "company_domain": "acme.com"}],
        "has_email": true,
        "email_status": "verified"
      },
      "meta": {"source_ids": {"person_id": "<person_id>"}}
    }
  ],
  "meta": {"count": 25, "limit": 25, "credits": 25, "nextCursor": "..."}
}

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-key headerRe-export $LEADOCEAN_API_KEY from app.leadocean.io and retry
429Above the plan's rate limit (5 a second on Free, 200 on Agency)Honor Retry-After, slow the loop, or move up a plan
402Records spent (Free is 1,000 for life)Check GET /v1/usage, then move to Solo at $97 a month
Empty resultsA filter value is not an exact enum stringCall leadocean_list_enum_values and copy the value
Search stops with meta.depthCappedOne search pages to 10,000 rowsNarrow the filters and run several searches
Export refusedOver 50,000 rows a request or 500,000 a daySplit the export by country or seniority

FAQ

Can Claude Code send the emails?

No. Claude Code drafts and organizes. Sending needs a sender with mailboxes, warm-up and unsubscribe handling. LeadOcean is the data layer, not a sequencer.

How many records does a test run cost?

About 50 for 25 leads: 25 for the search page and up to 25 for enrichment. Counts and filter lookups are free. See pricing.

Do I need the MCP server, or is REST enough?

REST is enough for a script. MCP is faster for exploring, because Claude can count and fix filters in conversation. Use both.

How do I keep the personalization honest?

Give Claude one source fact per line and a rule to leave the line empty when there is none. Review a sample every run. More patterns are in how to use AI to personalize cold emails.

What comes after the draft?

Sequencing, timing and follow-ups. Read cold email automation to sequences that convert and the cold email prompt.

Give Claude Code a list it can actually write to

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

Get your free API key →