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

How-to guide

How to Write 11 Personalized Sales Email Letters (With Examples From Real Data)

Every letter below hangs on one field you can query. Pull the field first, then write the line.

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

A personalized sales email is one specific fact about the reader, stated in the first line, that you could only know by looking them up. Pull the fact from data, then write around it. The 11 letters below each use one field.

All examples use placeholders: Acme, acme.com, Jane Doe. Requests go to https://api.leadocean.io with the x-api-key header.

TL;DR

  • Pick one field per letter (title, tech, funding, school, past employer), query it, then write the opening line from the value. About 40 minutes for the pull and three drafts.
  • Sizing costs 0 records. A first batch of 25 to 100 people costs 25 to 100 records, inside the 1,000 on the Free plan.
  • Personalization that needs a lookup (career history, tech stack) beats a first name merge. Skip any letter where the field comes back empty.

Prerequisites

  • A LeadOcean key from app.leadocean.io. Free has no card.
  • The key in your shell as $LEADOCEAN_API_KEY.
  • curl and jq.
bash
export LEADOCEAN_API_KEY="paste-your-key-here"

Step 1: Size the list before you write a word

Count first. Counting is free on every plan, through the leadocean_count_leads MCP tool or count=true as a query parameter with limit 1 on search.

bash
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"]}'

On 21 October 2026 the same filters through the MCP count tool returned 5,574 people. Each has a verified or catch-all email. They work at 3,873 distinct companies. Without the email condition the total is 18,800. Label your own count the same way: it describes the mailable audience, not everyone.

Enum values are exact strings (VP, not vp). Resolve them with leadocean_list_enum_values or GET /v1/enums before you guess.

Step 2: Pull the people with the field you will personalize on

Search body is flat JSON, filters are arrays, limit runs 1 to 100, and you paginate with cursor. Each filter below maps to one or more of the letters.

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": ["CA"],
    "emailStatus": ["verified"],
    "limit": 25
  }' > batch.json

Swap the filter to change the letter:

  • technologies or technologyCategories: letters 4 and 5.
  • minLastFundingYear plus fundingType: letter 6. Counts with funding filters are not supported, so size the list without them and expect fewer rows at search time.
  • education: letter 9.
  • keywords: letter 10 (a phrase from the person's own profile text).

Step 3: Enrich the people you will write to

Search rows hold the basics. For career history, enrich one person by person_id, which every search row carries at meta.source_ids.person_id. Enrich takes exactly one of linkedin_url, email, phone or person_id. A person we hold nothing on is a 404 and costs nothing.

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}'

Since 17 October 2026 the response carries resume_data: past roles, education, certifications, awards and publications, each when held. Past employers carry no company_id yet, so match them by name. The current role is in contact_data.contact_current_experiences[], with job_title, company_name and start_date.

Step 4: The 11 letters

Each letter is under 80 words and uses one field. Write the opening line from the value, keep the ask to one question, and let the subject line name the same fact. Do not stack two fields in one email. The bracketed value is the field you merge in. If the field is empty for a person, do not send that letter to them.

1. Role in the first line

Subject: Acme's sales team Jane, running sales at Acme means you own pipeline and hiring in the same quarter. We hand your reps a verified list by title so neither waits on ops. Worth a look?

Field: job_title and company_name from contact_data.contact_current_experiences[0].

2. Recent job change

Subject: New at Acme Jane, congrats on the move to Acme. New VPs usually rebuild the account list in the first 90 days. We can size yours today for free.

Field: start_date of contact_data.contact_current_experiences[0].

3. Past employer

Subject: From Globex to Acme Jane, you spent years at Globex before Acme. Teams that move between the two often keep the same list-building habit. Here is a faster way.

Field: company_name in resume_data.experiences[].

4. Tech you run

Subject: Acme and your CRM Jane, acme.com loads a CRM we detect on the site. Does your team enrich records after they land in it, or before?

Field: technologies filter, matched through the crawled-site data (about 7.8M sites).

5. Tech category gap

Subject: Email tooling at Acme Jane, we found no marketing automation on acme.com. If you are sending from a spreadsheet, there is a cleaner route.

Field: technologyCategories. A missing tag means not detected, not proven absent. Say "we found" and nothing stronger.

6. Funding round

Subject: After the round Jane, Acme raised a Series A in 2025. Companies at that stage usually add a first outbound hire. Do you have a list ready for them?

Field: fundingType and minLastFundingYear.

7. Headcount band

Subject: 51 to 200 people Jane, at Acme's size one ops person usually owns the CRM and the list. We built the API for that person.

Field: employeeRange.

8. Country or region

Subject: Selling into Canada Jane, we hold verified work emails for VPs of sales in Canada. If that is your next territory, I can send the count.

Field: country, plus the count from Step 1.

9. School

Subject: Fellow alum Jane, I saw you studied at Example University. I went there too and now work on sales data. Short question about your list process.

Field: education, or resume_data.educations[]. Only use this if it is true for you.

10. Something from their profile

Subject: Your note on outbound Jane, your profile mentions outbound for mid-market. One thing we see work for that segment: verified email only, no catch-all.

Field: keywords filter on profile text.

11. Certification or award

Subject: Congrats on the award Jane, saw the award listed on your profile. Reps who win those usually have the cleanest data. Is yours?

Field: resume_data.awards[] or certifications[].

Step 5: Merge and check before you send

Merge in a script, not by hand. Skip empty values.

python
import json
rows = json.load(open("batch.json"))["data"]
for r in rows:
    first = (r.get("profile_data") or {}).get("profile_first_name")
    roles = (r.get("contact_data") or {}).get("contact_current_experiences") or [{}]
    title, company = roles[0].get("job_title"), roles[0].get("company_name")
    if not (first and title and company):
        continue
    print(f"{first}, running {title} at {company}...")

Check contact_data.email_status on each row before sending. There is no refund for bounced emails. Send only verified unless you accept catch-all risk.

What you get

The count in Step 1 is a real run. The enrich response below is the shape from the API reference, with placeholder values.

json
{
  "success": true,
  "data": {
    "profile_data": { "profile_first_name": "Jane", "profile_full_name": "Jane Doe" },
    "contact_data": {
      "email_status": "verified",
      "contact_emails": [{ "email": "jane@acme.com", "status": "verified" }],
      "contact_current_experiences": [
        { "job_title": "VP Sales", "company_name": "Acme", "company_domain": "acme.com", "start_date": "2026-03-01" }
      ]
    },
    "resume_data": {
      "experiences": [{ "job_title": "Director of Sales", "company_name": "Globex", "start_date": "2021-01-01", "end_date": "2026-02-28" }],
      "educations": [{ "educational_establishment": "Example University", "diploma": "BA" }],
      "certifications": [],
      "awards": []
    },
    "meta": { "source_ids": { "person_id": "123456789" } }
  }
}

Response shape from the API reference (openapi.json and the enrich docs, October 2026), with placeholder values. Empty arrays mean we hold nothing for that block.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-keyCheck $LEADOCEAN_API_KEY is exported in this shell.
429Above the plan's rate limit (5 a second on Free, 200 on Agency)Wait for Retry-After, or move to a plan with a higher limit.
402Out of recordsThe Free plan holds 1,000 for life. Upgrade or narrow the pull.
Empty dataBad enum value, such as vp for VPResolve values with GET /v1/enums and use the exact string.
404 on enrichWe hold nothing for that personFree. Skip them and move on.
Search stops at 10,000 rowsmeta.depthCapped is trueNarrow the filters and run a second search.

FAQ

How many personalized lines should one email have?

One. A single specific fact in the first sentence does the work. Two or three reads like a dossier.

What if the field is empty for some people?

Send a different letter. Pick the next field, or drop the person from the batch. Never write a guess.

Can I do this without paying?

Yes. Free includes 1,000 records for life, every feature, 5 requests a second and no card. See pricing.

Where do I get more templates?

Pull the field, then write the line. Get your free API key.

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

Get your free API key →