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

How-to guide

How to Write 10 Personal Email Templates That Earn More Sales (With Real Merge Data)

A template feels personal when every variable is a fact about the reader. Pull the facts first, then fill ten templates.

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

A personal email names one fact the reader would recognize about themselves. The ten templates below each hang on a single data field, so you fill them from an API call instead of from guesswork.

TL;DR

  • Pull a small list with title, company and email status, then enrich a few people for career history.
  • It takes about 30 minutes and spends about 30 records, well inside the Free plan's 1,000.
  • Each template names the field it needs. If the field is empty for a person, skip that template for that person.

Prerequisites

  • A free LeadOcean API key in $LEADOCEAN_API_KEY. Sign up at app.leadocean.io, no card. Plans are on pricing.
  • curl and jq, or any HTTP client.
  • A sending tool of your choice. LeadOcean returns data only. It has no sequencer, inbox or CRM.
  • Optional: the hosted MCP server at https://api.leadocean.io/mcp if you would rather run the count from Claude.

Step 1: Size the list before you spend anything

Count first. A count spends no records, so run it as often as you like while you tune the filters.

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": ["Director"],
    "jobFunction": ["Sales & Business Development"],
    "country": ["US"],
    "employeeRange": ["51-200"],
    "limit": 1
  }' | jq '.meta'

count=true goes in the query string, because the JSON body rejects unknown fields. The response has no rows, only meta.total (capped at 100,000), meta.distinctCompanies and meta.countOnly: true.

On 2026-10-21 the same filters through the free MCP tool leadocean_count_leads returned 36,453 people with a verified, catch_all_valid or catch_all email, across 20,473 companies. Without the email condition they matched 119,788. Restricted to verified only, the count was 9,043 people at 6,241 companies. Note which number you quote and label it by the filter you used.

Step 2: Pull 25 people with a verified email

One search page of 25 is 25 records. Ask only for addresses you will send to.

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": ["Director"],
    "jobFunction": ["Sales & Business Development"],
    "country": ["US"],
    "employeeRange": ["51-200"],
    "emailStatus": ["verified"],
    "limit": 25
  }' > people.json

jq -r '.data[] | [.profile_data.profile_full_name, .profile_data.profile_headline, .meta.source_ids.person_id] | @tsv' people.json

Search is a flat JSON body, filters are arrays and limit runs 1 to 100. Page on with the meta.nextCursor value passed back as cursor. Search rows carry no contact points: you get the name, headline, email_status and a person_id to enrich.

The filters you searched with are merge data too. Every person in this file is a sales director at a US company of 51 to 200 people. Templates 4 and 10 use exactly that, with no extra call.

Step 3: Enrich five people for career history

Search gives you title and company. Enrich adds past roles, schools, certifications and awards, which are the facts that make a line feel researched. Enrich takes exactly one of linkedin_url, email, phone or person_id.

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}' > person.json

jq '.data.resume_data | {experiences: .experiences[0:2], educations: .educations[0:1], certifications, awards}' person.json

Past roles come back in resume_data.experiences[] with title, company name, start and end dates. The current role sits in contact_data.contact_current_experiences[]. Each array appears only when held, so test for it before you merge. A person we hold nothing for is a 404 and costs nothing. Past employers carry no company_id yet, so match them by name.

Enrich five people, not 25. Read what came back, decide which templates you can fill, then enrich more only for the templates that work.

Step 4: Fill the ten templates

Each template needs one field. Keep every email under 90 words and ask one question. Merge fields are in {braces}; rename them to whatever your sending tool uses.

text
1. Role change      Subject: {first_name}, congrats on {new_title}
   Needs: contact_current_experiences[] start date, recent.
   Body: "Saw you took over {team} at {company} recently. New roles often
   bring a tooling review. Worth 10 minutes?"

2. Past employer    Subject: From {past_company} to {company}
   Needs: resume_data.experiences[].
   Body: "You went from {past_title} at {past_company} to {title} here.
   How different is the buying process?"

3. School           Subject: {school}
   Needs: resume_data.educations[]. Use only if you share it.
   Body: "Another {school} grad in {function}. Quick question about how
   {company} handles {problem}."

4. Company size     Subject: Running {function} at {headcount} people
   Needs: the employeeRange you searched with.
   Body: "At your size, one person often owns {problem} on top of the
   day job. Is that you?"

5. Tech stack       Subject: {technology} at {company}
   Needs: the technologies filter you searched with (crawled sites only).
   Body: "Noticed {company} runs {technology}. Does {problem} come up
   on your side?"

6. Award            Subject: Congrats on {award}
   Needs: resume_data.awards[].
   Body: "{award} is hard to get. What changed in how your team sold
   that year?"

7. Certification    Subject: {certification}
   Needs: resume_data.certifications[].
   Body: "You hold {certification}, so {problem} is likely on your
   radar. Can I send a two-line idea?"

8. Peer count       Subject: Two others at {company}
   Needs: GET /v1/companies/{domain}/people.
   Body: "I am writing to {peer_title} too, but you own {function}.
   Is that the right split?"

9. Funding          Subject: After the {round}
   Needs: fundingType at search time (not in free counts).
   Body: "Congrats on the {round}. Is {process} on the list for the
   next year at {company}?"

10. Plain ask       Subject: Quick question, {first_name}
    Needs: title and company only.
    Body: "I help {title}s at {headcount}-person companies with
    {problem}. Is it on your list this quarter?"

Merge in your own script or sending tool. Skip any template whose field is empty. Never write a line the data does not support, and never claim a shared school or employer you do not share.

Which field each template trusts

Templates 1, 2, 6 and 7 come from enrich. They cost one record per person and only work when the array is held. Expect gaps, and test for the array before you merge.

Templates 4, 5 and 9 come from the search filters, not the record. If you searched for employeeRange 51-200, every row is in that band, so the line is true for all of them. The same goes for technologies and fundingType.

Template 8 needs a second call: GET /v1/companies/{domain}/people lists the other people at one domain. Each person returned is one record, so use it only on your top accounts. Template 3 needs a school you share. Do not send it otherwise.

Template 10 needs nothing but the search row. It is the control: every other template should beat it, or you drop it.

Writing the personal line

The personal line is the first sentence, and it states the fact plainly. "You went from account executive at Acme to sales director here" works. "I was so impressed by your journey" does not, because it says nothing the reader can check.

Keep the fact and the ask in separate sentences. The fact earns the read. The ask earns the reply. When one sentence tries to do both, it reads like a mail merge.

Do not stack fields. One email with a school, an award and a past employer reads as surveillance, not research. Pick the strongest field for that person and leave the others for a follow-up.

Read every filled email before it goes out for the first batch. Merge errors show up fast: an empty field leaves "at ," in the line, and a job title in capitals reads like a database export. Fix the template, not the email, so the next batch is clean.

How to test them

Send each template to at least 50 people before you judge it. Track replies per template, not per campaign. Keep the subject line and the ask the same, and change only the personal line, so you learn which field earns the reply.

Step 5: Check the email status before sending

Read email_status on every row before it reaches your sending tool. LeadOcean does not refund bounces.

bash
jq -r '.data[] | select(.contact_data.email_status=="verified") | .meta.source_ids.person_id' people.json | wc -l

Send to verified first. Treat catch_all_valid and catch_all as a second wave, after you know your bounce rate on the first.

What you get

Output of leadocean_count_leads on 2026-10-21 for jobLevel Director, jobFunction Sales & Business Development, employeeRange 51-200, country US. The total counts people whose best email is verified, catch_all_valid or catch_all.

json
{
  "total": 36453,
  "totalIsExact": true,
  "totalAll": 119788,
  "distinctCompanies": 20473,
  "credits": 0
}

Response shape from the API reference, with placeholder values. It is not a real person.

json
{
  "success": true,
  "data": [
    {
      "profile_data": {
        "profile_full_name": "Jane Doe",
        "profile_headline": "Director of Sales at Acme",
        "profile_url": "https://www.linkedin.com/in/jane-doe"
      },
      "contact_data": {
        "has_email": true,
        "has_phone": false,
        "email_status": "verified",
        "email_type": "work"
      },
      "meta": { "source_ids": { "person_id": "1234567890" } }
    }
  ],
  "meta": { "count": 1, "limit": 25, "credits": 1, "nextCursor": "CURSOR" }
}

Enrich returns the same person under data, plus resume_data with experiences[], educations[], certifications[] and awards[], each present only when held.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-keyCopy the key from app.leadocean.io and export it again
429Above the plan rate limit (5 a second on Free, 200 on Agency)Honor Retry-After, or move to a higher plan
402Records spent (Free has 1,000 for life)Check GET /v1/usage, then pick a plan on pricing
Empty resultsA bad enum value, such as director instead of DirectorCheck exact strings with leadocean_list_enum_values
404 on enrichNothing held for that personNo cost. Try another person_id
Export refusedOver 50,000 rows a request or 500,000 a day, or on FreeSplit the export. Bulk CSV is not on Free

FAQ

Does a personal template mean writing each email by hand?

No. Write ten templates once, fill them from data and review the first 20 sends by hand.

Which template should I start with?

Start with number 10 as the control, then add one field-driven template at a time. Compare replies before you add the next.

How many records does this cost?

About 30 here: 25 for the search page and 5 enrichments. Counting costs nothing, and an enrichment that finds nobody costs nothing.

Can I get the data in a spreadsheet instead?

Yes, on a paid plan. The app has an Exports page with a filter builder, a column picker and the record price before you start. Solo is $97 a month and Agency $497 a month.

What else should I read?

Fill your next ten templates with real data

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

Get your free API key →