ExamplesOutbound
Sales Outreach Agent
Automated sales email sequences
Overview
A sales outreach agent automates personalized email campaigns, tracks engagement, and follows up intelligently based on recipient behavior. This example demonstrates sending, tracking, and multi-step sequences.
Note
Sending emails requires a paid tier. See the Magic Upgrade Flow for autonomous tier upgrades.
How It Works
- Create an inbox on a paid tier
- Build a prospect list with personalization data
- Send personalized emails via the API
- Track replies and engagement via webhooks
- Follow up automatically based on response behavior
- Maintain conversation context via threads
Complete Implementation
import { DaimonClient } from 'daimon-email';
import express from 'express';
const client = new DaimonClient({ apiKey: process.env.DAIMON_API_KEY });
const app = express();
app.use(express.json());
interface Prospect {
email: string;
name: string;
company: string;
title: string;
painPoint?: string;
}
interface CampaignState {
prospectEmail: string;
stepNumber: number;
lastSent: Date;
replied: boolean;
threadId?: string;
}
const campaignStates = new Map<string, CampaignState>();
// Step 1: Set up sales inbox with paid tier
async function setupSalesInbox() {
const inbox = await client.inboxes.create({
username: 'sales-outreach',
clientId: 'sales-agent-v1'
});
console.log(`Sales inbox: ${inbox.address}`);
// Check capabilities
const authedClient = new DaimonClient({ apiKey: inbox.apiKey });
const capabilities = await authedClient.account.getCapabilities();
const canSend = capabilities.can.some(c => c.action === 'send_messages');
if (!canSend) {
console.log('⚠️ Sending requires paid tier');
console.log('Generate upgrade link:');
const upgradeLink = await authedClient.account.createUpgradeLink();
console.log(upgradeLink.url);
throw new Error('SEND_REQUIRES_PAID');
}
// Register webhook for replies
const webhook = await authedClient.webhooks.create({
endpointUrl: 'https://your-sales-agent.com/webhook',
events: ['message.received'],
inboxId: inbox.id
});
console.log('Webhook registered for reply tracking');
return { inbox, authedClient };
}
// Step 2: Build prospect list
const prospects: Prospect[] = [
{
email: 'john@techstartup.com',
name: 'John',
company: 'TechStartup Inc',
title: 'CTO',
painPoint: 'scaling infrastructure'
},
{
email: 'sarah@saascorp.io',
name: 'Sarah',
company: 'SaaS Corp',
title: 'VP Engineering',
painPoint: 'reducing deployment time'
}
];
// Step 3: Send personalized outreach
async function sendInitialOutreach(inboxId: string, prospect: Prospect) {
const subject = `Quick question about ${prospect.company}'s ${prospect.painPoint}`;
const body = `Hi ${prospect.name},
I noticed ${prospect.company} is growing quickly and thought you might be interested in how we've helped similar companies solve ${prospect.painPoint}.
We recently helped a company like yours reduce deployment time by 60% using our platform.
Would you be open to a quick 15-minute call next week to explore if we could help ${prospect.company} achieve similar results?
Best regards,
Sales Agent`;
try {
const message = await client.inboxes.send(inboxId, {
to: prospect.email,
subject,
body,
clientId: `outreach-${prospect.email}-step-1` // Idempotency
});
console.log(`Sent initial outreach to ${prospect.email}`);
// Track campaign state
campaignStates.set(prospect.email, {
prospectEmail: prospect.email,
stepNumber: 1,
lastSent: new Date(),
replied: false,
threadId: message.threadId
});
return message;
} catch (error) {
if (error.code === 'SEND_LIMIT_EXCEEDED') {
console.log('Send limit exceeded. Waiting before retry...');
// Handle rate limiting
}
throw error;
}
}
// Step 4: Handle replies via webhook
app.post('/webhook', async (req, res) => {
const { event, message } = req.body;
if (event === 'message.received') {
await handleReply(message);
}
res.status(200).send('OK');
});
async function handleReply(message: any) {
const prospectEmail = message.from;
console.log(`Reply received from ${prospectEmail}`);
console.log(`Subject: ${message.subject}`);
console.log(`Body: ${message.replyBody}`);
// Update campaign state
const state = campaignStates.get(prospectEmail);
if (state) {
state.replied = true;
campaignStates.set(prospectEmail, state);
}
// Analyze sentiment/intent
const isPositive = await analyzeReply(message.replyBody);
if (isPositive) {
// Send calendar link or next step
await sendCalendarLink(message.inboxId, message.id, prospectEmail);
} else {
// Graceful disengagement
await sendDisengagementReply(message.inboxId, message.id);
}
}
// Step 5: Follow-up sequence
async function runFollowUpSequence(inboxId: string) {
for (const [prospectEmail, state] of campaignStates.entries()) {
// Skip if already replied
if (state.replied) continue;
const daysSinceLastSent = (Date.now() - state.lastSent.getTime()) / (1000 * 60 * 60 * 24);
// Send follow-up after 3 days
if (state.stepNumber === 1 && daysSinceLastSent >= 3) {
await sendFollowUp1(inboxId, prospectEmail, state);
}
// Send second follow-up after 6 days
if (state.stepNumber === 2 && daysSinceLastSent >= 6) {
await sendFollowUp2(inboxId, prospectEmail, state);
}
// Final follow-up after 10 days
if (state.stepNumber === 3 && daysSinceLastSent >= 10) {
await sendFinalFollowUp(inboxId, prospectEmail, state);
}
}
}
async function sendFollowUp1(inboxId: string, prospectEmail: string, state: CampaignState) {
const prospect = prospects.find(p => p.email === prospectEmail);
const body = `Hi ${prospect.name},
Following up on my previous email about ${prospect.company}'s ${prospect.painPoint}.
I wanted to share a quick case study from a similar company that saw 3x improvement in their deployment pipeline.
Is this something you'd be interested in exploring?
Best,
Sales Agent`;
await client.inboxes.messages.reply(inboxId, state.threadId, {
body,
clientId: `outreach-${prospectEmail}-step-2`
});
state.stepNumber = 2;
state.lastSent = new Date();
campaignStates.set(prospectEmail, state);
console.log(`Sent follow-up 1 to ${prospectEmail}`);
}
async function sendFollowUp2(inboxId: string, prospectEmail: string, state: CampaignState) {
const prospect = prospects.find(p => p.email === prospectEmail);
const body = `Hi ${prospect.name},
I know you're busy, so I'll keep this brief.
We have a limited number of spots for our Q2 onboarding cohort, and I thought ${prospect.company} would be a great fit.
If you're interested in learning more, I'd love to schedule a quick call.
Otherwise, I'll assume the timing isn't right and won't follow up again.
Best,
Sales Agent`;
await client.inboxes.messages.reply(inboxId, state.threadId, {
body,
clientId: `outreach-${prospectEmail}-step-3`
});
state.stepNumber = 3;
state.lastSent = new Date();
campaignStates.set(prospectEmail, state);
console.log(`Sent follow-up 2 to ${prospectEmail}`);
}
async function sendFinalFollowUp(inboxId: string, prospectEmail: string, state: CampaignState) {
const body = `No worries! I'll close this out on my end.
If anything changes or you'd like to revisit this in the future, feel free to reach out.
Best of luck!
Sales Agent`;
await client.inboxes.messages.reply(inboxId, state.threadId, {
body,
clientId: `outreach-${prospectEmail}-final`
});
console.log(`Sent final follow-up to ${prospectEmail}`);
}
async function sendCalendarLink(inboxId: string, messageId: string, prospectEmail: string) {
const body = `Great! Here's a link to book a time that works for you:
https://calendly.com/sales-agent/demo
Looking forward to connecting!
Best,
Sales Agent`;
await client.inboxes.messages.reply(inboxId, messageId, {
body,
clientId: `calendar-${prospectEmail}`
});
console.log(`Sent calendar link to ${prospectEmail}`);
}
async function sendDisengagementReply(inboxId: string, messageId: string) {
const body = `Thanks for letting me know. I appreciate your time.
Best of luck!
Sales Agent`;
await client.inboxes.messages.reply(inboxId, messageId, {
body,
clientId: `disengage-${messageId}`
});
}
async function analyzeReply(replyBody: string): Promise<boolean> {
// Simple sentiment analysis (could use LLM)
const positiveKeywords = ['interested', 'yes', 'sure', 'sounds good', 'tell me more'];
const negativeKeywords = ['not interested', 'no thanks', 'remove', 'unsubscribe'];
const lowerBody = replyBody.toLowerCase();
if (negativeKeywords.some(kw => lowerBody.includes(kw))) {
return false;
}
if (positiveKeywords.some(kw => lowerBody.includes(kw))) {
return true;
}
return false; // Default to negative if unclear
}
// Run the sales campaign
async function runSalesCampaign() {
const { inbox, authedClient } = await setupSalesInbox();
// Send initial outreach to all prospects
for (const prospect of prospects) {
await sendInitialOutreach(inbox.id, prospect);
await new Promise(resolve => setTimeout(resolve, 2000)); // Rate limiting
}
// Run follow-up sequence daily
setInterval(async () => {
await runFollowUpSequence(inbox.id);
}, 24 * 60 * 60 * 1000); // Daily
// Start webhook server
app.listen(3000, () => {
console.log('Sales agent running on port 3000');
});
}
runSalesCampaign();from daimon_email import DaimonClient
from flask import Flask, request
import os
import time
from datetime import datetime, timedelta
from typing import Dict, List
client = DaimonClient(api_key=os.environ.get('DAIMON_API_KEY'))
app = Flask(__name__)
campaign_states = {}
# Step 1: Set up sales inbox with paid tier
def setup_sales_inbox():
inbox = client.inboxes.create(
username='sales-outreach',
client_id='sales-agent-v1'
)
print(f"Sales inbox: {inbox.address}")
# Check capabilities
authed_client = DaimonClient(api_key=inbox.api_key)
capabilities = authed_client.account.get_capabilities()
can_send = any(c['action'] == 'send_messages' for c in capabilities['can'])
if not can_send:
print('⚠️ Sending requires paid tier')
print('Generate upgrade link:')
upgrade_link = authed_client.account.create_upgrade_link()
print(upgrade_link['url'])
raise Exception('SEND_REQUIRES_PAID')
# Register webhook for replies
webhook = authed_client.webhooks.create(
endpoint_url='https://your-sales-agent.com/webhook',
events=['message.received'],
inbox_id=inbox.id
)
print('Webhook registered for reply tracking')
return inbox, authed_client
# Step 2: Build prospect list
prospects = [
{
'email': 'john@techstartup.com',
'name': 'John',
'company': 'TechStartup Inc',
'title': 'CTO',
'pain_point': 'scaling infrastructure'
},
{
'email': 'sarah@saascorp.io',
'name': 'Sarah',
'company': 'SaaS Corp',
'title': 'VP Engineering',
'pain_point': 'reducing deployment time'
}
]
# Step 3: Send personalized outreach
def send_initial_outreach(inbox_id: str, prospect: Dict):
subject = f"Quick question about {prospect['company']}'s {prospect['pain_point']}"
body = f"""Hi {prospect['name']},
I noticed {prospect['company']} is growing quickly and thought you might be interested in how we've helped similar companies solve {prospect['pain_point']}.
We recently helped a company like yours reduce deployment time by 60% using our platform.
Would you be open to a quick 15-minute call next week to explore if we could help {prospect['company']} achieve similar results?
Best regards,
Sales Agent"""
try:
message = client.inboxes.send(inbox_id, {
'to': prospect['email'],
'subject': subject,
'body': body,
'client_id': f"outreach-{prospect['email']}-step-1"
})
print(f"Sent initial outreach to {prospect['email']}")
# Track campaign state
campaign_states[prospect['email']] = {
'prospect_email': prospect['email'],
'step_number': 1,
'last_sent': datetime.now(),
'replied': False,
'thread_id': message['thread_id']
}
return message
except Exception as e:
if hasattr(e, 'code') and e.code == 'SEND_LIMIT_EXCEEDED':
print('Send limit exceeded. Waiting before retry...')
raise
# Step 4: Handle replies via webhook
@app.post('/webhook')
def handle_webhook():
data = request.json
event = data.get('event')
message = data.get('message')
if event == 'message.received':
handle_reply(message)
return 'OK', 200
def handle_reply(message: Dict):
prospect_email = message['from']
print(f"Reply received from {prospect_email}")
print(f"Subject: {message['subject']}")
print(f"Body: {message.get('reply_body')}")
# Update campaign state
if prospect_email in campaign_states:
campaign_states[prospect_email]['replied'] = True
# Analyze sentiment/intent
is_positive = analyze_reply(message.get('reply_body', ''))
if is_positive:
send_calendar_link(message['inbox_id'], message['id'], prospect_email)
else:
send_disengagement_reply(message['inbox_id'], message['id'])
def analyze_reply(reply_body: str) -> bool:
positive_keywords = ['interested', 'yes', 'sure', 'sounds good', 'tell me more']
negative_keywords = ['not interested', 'no thanks', 'remove', 'unsubscribe']
lower_body = reply_body.lower()
if any(kw in lower_body for kw in negative_keywords):
return False
if any(kw in lower_body for kw in positive_keywords):
return True
return False
def send_calendar_link(inbox_id: str, message_id: str, prospect_email: str):
body = """Great! Here's a link to book a time that works for you:
https://calendly.com/sales-agent/demo
Looking forward to connecting!
Best,
Sales Agent"""
client.inboxes.messages.reply(inbox_id, message_id, {
'body': body,
'client_id': f"calendar-{prospect_email}"
})
print(f"Sent calendar link to {prospect_email}")
def send_disengagement_reply(inbox_id: str, message_id: str):
body = """Thanks for letting me know. I appreciate your time.
Best of luck!
Sales Agent"""
client.inboxes.messages.reply(inbox_id, message_id, {
'body': body,
'client_id': f"disengage-{message_id}"
})
# Run the sales campaign
def run_sales_campaign():
inbox, authed_client = setup_sales_inbox()
# Send initial outreach to all prospects
for prospect in prospects:
send_initial_outreach(inbox['id'], prospect)
time.sleep(2) # Rate limiting
# Start webhook server
app.run(port=3000)
if __name__ == '__main__':
run_sales_campaign()Key Features
- Personalization: Dynamic content based on prospect data
- Thread continuity: All follow-ups stay in the same conversation
- Reply tracking: Webhooks for instant engagement detection
- Multi-step sequences: Automated follow-ups based on timing
- Idempotency: Prevents duplicate sends with
clientId