| name | sdr-agent |
| description | AI SDR agent patterns: prospect research, personalised outreach, multi-step sequences, reply handling, meeting booking — with safety limits |
SDR Agent Skill
When to activate
- Building an AI-powered sales development workflow
- Automating prospect research and outreach message generation
- Setting up multi-touch email/LinkedIn sequences with Claude
- Personalising outreach at scale (not template swapping — actual context)
- Adding safety limits and human approval gates to outreach automation
When NOT to use
- Spam campaigns — volume without personalisation kills deliverability and reputation
- LinkedIn automation without understanding connection limits (100-200/week max)
- Replacing human relationship-building on strategic accounts
- Regulated industries with strict communication compliance requirements
Instructions
The SDR agent loop
Step 1: RESEARCH — gather company/contact context
Step 2: QUALIFY — score against ICP (Ideal Customer Profile)
Step 3: PERSONALISE — generate message with genuine context
Step 4: HUMAN GATE — show draft, get approval (for first outreach)
Step 5: SEND — deliver via email/LinkedIn
Step 6: TRACK — log activity, handle replies
Step 7: FOLLOW UP — sequence next touch if no reply
Prospect research (before writing a single word)
interface ProspectContext {
name: string
title: string
company: string
recentNews: string[]
linkedInActivity: string[]
techStack: string[]
painPoints: string[]
icpScore: number
}
async function researchProspect(email: string): Promise<ProspectContext> {
const [contact, company, news] = await Promise.all([
enrichContactFromEmail(email),
enrichCompanyFromDomain(getDomain(email)),
searchRecentNews(company.name),
])
const icpScore = scoreICP(contact, company)
const painPoints = await inferPainPoints(company, contact.title)
return { ...contact, ...company, recentNews: news, icpScore, painPoints }
}
ICP scoring
interface ICPCriteria {
companySize: [number, number]
industries: string[]
titles: string[]
techStack: string[]
geographies: string[]
}
function scoreICP(contact: Contact, company: Company, criteria: ICPCriteria): number {
let score = 0
const [min, max] = criteria.companySize
if (company.employees >= min && company.employees <= max) score += 30
if (criteria.industries.some(i => company.industry.toLowerCase().includes(i))) score += 25
if (criteria.titles.some(t => contact..().(t))) score +=
overlap = criteria..( company..(t))
score += .(, overlap. * )
score
}
Personalised message generation
The key difference between AI spam and genuine personalisation is specificity. Claude generates messages that reference exactly what makes this prospect relevant right now.
async function generateOutreachMessage(
prospect: ProspectContext,
sender: SenderContext,
template: MessageTemplate
): Promise<string> {
const prompt = `Write a cold outreach email from ${sender.name} at ${sender.company} to ${prospect.name}.
Context about ${prospect.name}:
- Title: ${prospect.title} at ${prospect.company}
- Recent company news: ${prospect.recentNews.slice(0, 2).join('; ')}
- Their likely pain points: ${prospect.painPoints.join(', ')}
- Why we're relevant: ${template.valueProposition}
Rules:
- Reference ONE specific recent event or achievement (not generic flattery)
- State the value in 1 sentence — what specific outcome we deliver
- Clear, low-friction CTA: "15-minute call this week?" not "I'd love to connect"
- Total length: 5-7 sentences MAX
- No buzzwords: no "synergies", "leverage", "circle back", "reach out"
- First line must NOT start with "I" or "My name is"
- Do not mention competitors
Output: just the email body, no subject line.`
const { text } = await generateText({ model: anthropic('claude-opus-4-7'), prompt })
return text
}
Multi-step sequence design
const SEQUENCE: SequenceStep[] = [
{
day: 0,
channel: 'email',
type: 'initial',
subject: '{{personalised_hook}}',
waitForReply: true,
},
{
day: 3,
channel: 'linkedin',
type: 'connection',
message: 'Short note referencing the email — 2 sentences max',
waitForReply: true,
},
{
day: 7,
channel: 'email',
type: 'followup_1',
subject: 'Re: {{original_subject}}',
message: 'Add a new piece of value — case study, relevant data point',
waitForReply: true,
},
{
day: 14,
channel: 'email',
type: 'breakup',
subject: 'Closing the loop',
message: 'Acknowledge they\'re busy. Leave door open. No guilt.',
waitForReply: false,
},
]
Safety limits and compliance
const SAFETY_LIMITS = {
linkedInConnectionsPerWeek: 100,
emailsPerDay: 50,
minDelayBetweenMessages: 30_000,
maxFollowUps: 3,
blacklistDomains: [
'competitor.com',
'investor.com',
],
requireHumanApproval: true,
}
function checkSafetyLimits(prospect: ProspectContext): SafetyResult {
if (SAFETY_LIMITS.blacklistDomains.includes(getDomain(prospect.email))) {
return { allowed: false, reason: 'Domain blacklisted' }
}
return { allowed: true }
}
Human approval gate
async function requestApproval(draft: OutreachDraft): Promise<boolean> {
console.log('\n=== OUTREACH DRAFT FOR APPROVAL ===')
console.log(`To: ${draft.prospect.name} <${draft.prospect.email}>`)
console.log(`Company: ${draft.prospect.company}`)
console.log(`Score: ${draft.prospect.icpScore}/100`)
console.log(`\nSubject: ${draft.subject}`)
console.log(`\n${draft.body}`)
console.log('\nApprove? (y/n/edit): ')
const response = await getUserInput()
return response.toLowerCase() === 'y'
}
Reply handling
async function handleReply(reply: EmailReply): Promise<void> {
const intent = await classifyReply(reply.body)
switch (intent) {
case 'interested':
await bookMeeting(reply.from, reply.threadId)
await updateCRM(reply.from, { status: 'meeting_booked' })
break
case 'not_now':
await scheduleFutureFollowUp(reply.from, days: 90)
break
case 'not_interested':
await markAsOptedOut(reply.from)
break
case 'referral':
const referredContact = await extractReferral(reply.body)
await addToSequence(referredContact)
break
}
}
async function classifyReply(: ): <> {
{ } = ({
: (),
: z.({ : z.([, , , , , ]) }),
: ,
})
.
}
Example
User: Build an SDR agent that takes a list of startup founders (name + LinkedIn URL), researches each one, generates a personalised cold email about our product (a B2B analytics tool), and shows me the drafts for approval before sending.
Expected output:
src/sdr/research.ts — researchProspect() pulling from LinkedIn, Clearbit
src/sdr/qualify.ts — scoreICP() against startup founder criteria
src/sdr/generate.ts — generateOutreachEmail() with Claude, referencing recent activity
src/sdr/approve.ts — CLI approval loop showing draft + prospect context
src/sdr/send.ts — sends via SendGrid/Resend on approval, logs to HubSpot
- Safety: checks blacklist, respects 50/day email limit, requires approval