How to Build a Claude-Powered Sales Email Generator
What you're actually building
A Claude-powered sales email generator is a small application that takes structured lead data — company name, role, pain point, past interaction notes — and turns it into a personalized, on-brand outbound email in seconds. The hard part isn't calling an LLM; it's getting consistent tone, avoiding generic AI-sounding copy, and wiring the generator into whatever your sales team already uses (a CRM, a CSV export, a Slack command).
This guide walks through the whole thing: the input schema, the prompt that reliably produces usable emails, the API call itself, and how to scale it from "one email" to "two hundred personalized emails a morning" without rate-limit headaches.
Step 1: Define what goes in, not just what comes out
Most bad AI sales emails come from bad prompts, and most bad prompts come from vague inputs. Before writing a single line of code, define a strict input schema:
{
"lead": {
"name": "Jordan Price",
"title": "VP of Operations",
"company": "Harbor Logistics",
"industry": "freight & logistics"
},
"context": {
"pain_point": "manual shipment tracking across 3 spreadsheets",
"trigger_event": "recently raised Series B",
"past_touch": "downloaded our ROI calculator last week"
},
"sender": {
"name": "Alex",
"company": "RouteFlow",
"value_prop": "real-time shipment visibility without new hardware"
},
"tone": "direct, no buzzwords, under 120 words"
}
Every field you add here reduces the amount of guessing Claude has to do — which directly improves output quality.
Step 2: Write a prompt that produces usable emails, not filler
Sales emails fail for predictable reasons: too long, too generic, too salesy in the first line. Encode those constraints explicitly instead of hoping the model infers them.
You are a sales copywriter for {sender.company}. Write a cold outbound
email to {lead.name}, {lead.title} at {lead.company}.
Rules:
- First sentence must reference {context.trigger_event} or {context.pain_point}, not the sender's company.
- Max 120 words total.
- One clear call to action: a 15-minute call.
- No buzzwords: "synergy", "revolutionize", "game-changer", "leverage".
- No exclamation marks.
- Sign off with just the sender's first name.
Lead context: {context.pain_point}, {context.past_touch}
Value proposition: {sender.value_prop}
Output only the email body, no subject line, no explanation.
Separating the subject line into its own call (or its own instruction block) usually produces better subject lines than asking Claude to do both at once — subject lines benefit from a tighter, punchier prompt.
Step 3: Call the API
This is where you need a Claude integration that's stable enough to run in production without you managing retries, key rotation, or billing per request yourself. SubToAPI exposes your Claude access as a normal HTTPS API, so the generator is just a fetch call:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4",
"max_tokens": 300,
"messages": [
{"role": "user", "content": "Write a cold email to Jordan Price, VP of Operations at Harbor Logistics..."}
]
}'
Or in JavaScript, which is more useful once this is wired into a CRM or internal tool:
async function generateEmail(lead, context, sender, tone) {
const prompt = buildPrompt(lead, context, sender, tone);
const res = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 300,
messages: [{ role: "user", content: prompt }]
})
});
const data = await res.json();
return data.content[0].text;
}
Full request and response formats are in the messages docs, and the quickstart covers setting up your first sub_live_ key if you haven't generated one yet.
Step 4: Generate at batch scale without blowing through limits
Sales teams rarely want one email — they want a list of 50 leads turned into 50 drafts before the morning stand-up. The naive approach (a for loop firing requests sequentially) is slow; firing them all at once can trip rate limits. A simple concurrency cap works well:
async function generateBatch(leads, concurrency = 5) {
const results = [];
for (let i = 0; i < leads.length; i += concurrency) {
const batch = leads.slice(i, i + concurrency);
const emails = await Promise.all(batch.map(lead => generateEmail(lead)));
results.push(...emails);
}
return results;
}
If your team is generating emails across multiple reps, team seats (available on SubToAPI's Team and Scale plans) let each rep use their own key while usage rolls up into one dashboard — useful for tracking which campaigns are driving API cost.
Step 5: Stream the draft into your UI
If you're building this as an internal tool rather than a batch script, streaming the response token-by-token makes the tool feel instant instead of making the rep stare at a spinner for three seconds. SubToAPI supports streaming the same way the standard Claude API does — see the streaming docs for the event format and a working example.
Step 6: Optional — enrich leads automatically with tool use
If your lead data is thin (just a name and company domain), you can give Claude a tool that looks up company info — firmographics, recent news, funding rounds — before writing the email. This turns "generic template with a name swapped in" into genuinely researched outreach. The tool use docs cover defining and handling tool calls if you want to add this step.
A note on quality control
Don't ship Claude's output straight into a send queue. Add a lightweight review step: flag emails over the word limit, flag any that still contain banned buzzwords (a simple string check catches most), and sample a handful manually each week. The prompt gets you 90% of the way; a five-second human glance catches the rest.
Getting set up takes a few minutes — grab an API key via signup and follow the quickstart to send your first request.
Frequently asked questions
Does Claude write emails that sound obviously AI-generated? Not if the prompt is specific. Vague prompts ("write a sales email about our product") produce generic copy. Prompts with explicit constraints — word count, banned phrases, a required first-sentence hook tied to real lead data — produce usable drafts.
Can I generate subject lines and bodies in one call? You can, but quality improves if you separate them. Ask for the body first, then make a second, shorter call asking for 3 subject line options based on the generated body.
How do I keep API costs predictable when generating hundreds of emails? Cap max_tokens tightly (300 is plenty for a 120-word email), batch requests with a concurrency limit instead of firing them all at once, and check usage metadata in your SubToAPI dashboard to catch runaway loops early.