daimon.email
ExamplesOutbound

Cold Email Sequence

Multi-step email sequences with scheduling

Overview

A cold email sequence agent orchestrates multi-step campaigns with scheduled sends, automatic follow-ups, and intelligent sequence cancellation based on recipient behavior. This example demonstrates the Drafts API for scheduling future sends.

Note

Sending emails requires a paid tier. This example uses the Drafts API to schedule sends and manage sequences.

How It Works

  1. Create a multi-step sequence template (3-5 emails)
  2. Schedule all steps as drafts with send_at timestamps
  3. Monitor for replies via webhooks
  4. Cancel remaining drafts if prospect replies
  5. Respect send limits and timing rules
  6. Track sequence performance metrics

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

interface SequenceStep {
  dayOffset: number; // Days after sequence start
  subject: string;
  bodyTemplate: (prospect: Prospect) => string;
}

// Step 1: Define sequence template
const coldSequence: SequenceStep[] = [
  {
    dayOffset: 0,
    subject: 'Quick question about {{company}}',
    bodyTemplate: (p) => `Hi ${p.name},

I noticed ${p.company} is in the [industry] space and thought you might be interested in how we're helping similar companies [specific value prop].

Would love to share a quick case study if you're open to it.

Best,
Sales Agent`
  },
  {
    dayOffset: 3,
    subject: 'Re: Quick question about {{company}}',
    bodyTemplate: (p) => `Hi ${p.name},

Following up on my previous email.

I wanted to share a quick win from a company similar to ${p.company} - they saw a 3x improvement in [metric] within 30 days.

Worth a 10-minute call to explore?

Best,
Sales Agent`
  },
  {
    dayOffset: 7,
    subject: 'Re: Quick question about {{company}}',
    bodyTemplate: (p) => `${p.name},

I know you're busy, so I'll keep this brief.

We have a limited number of Q2 onboarding slots, and I think ${p.company} would be a perfect fit.

If you're interested, I'd love to share more details. Otherwise, I'll assume the timing isn't right.

Best,
Sales Agent`
  },
  {
    dayOffset: 14,
    subject: 'Re: Quick question about {{company}}',
    bodyTemplate: (p) => `${p.name},

No worries if the timing isn't right!

I'll close this out on my end. Feel free to reach out if anything changes in the future.

Best of luck with ${p.company}!

Sales Agent`
  }
];

// Step 2: Schedule entire sequence as drafts
async function scheduleSequence(inboxId: string, prospect: Prospect) {
  console.log(`Scheduling sequence for ${prospect.email}`);

  const now = new Date();
  const draftIds: string[] = [];

  for (const step of coldSequence) {
    const sendAt = new Date(now.getTime() + step.dayOffset * 24 * 60 * 60 * 1000);

    const subject = step.subject.replace('{{company}}', prospect.company);
    const body = step.bodyTemplate(prospect);

    // Create draft with scheduled send time
    const draft = await client.drafts.create(inboxId, {
      to: prospect.email,
      subject,
      body,
      sendAt: sendAt.toISOString(),
      clientId: `sequence-${prospect.email}-step-${step.dayOffset}`,
      metadata: {
        sequenceId: `sequence-${prospect.email}`,
        stepNumber: coldSequence.indexOf(step),
        prospectEmail: prospect.email
      }
    });

    draftIds.push(draft.id);

    console.log(`  Step ${step.dayOffset}: Scheduled for ${sendAt.toISOString()}`);
  }

  // Store draft IDs for potential cancellation
  return {
    sequenceId: `sequence-${prospect.email}`,
    draftIds,
    prospect
  };
}

// Step 3: Monitor for replies and cancel sequence
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} - canceling remaining sequence`);

  // Find and cancel all pending drafts for this prospect
  const drafts = await client.drafts.list(message.inboxId, {
    status: 'scheduled'
  });

  const prospectDrafts = drafts.filter(d =>
    d.metadata?.prospectEmail === prospectEmail &&
    d.to === prospectEmail
  );

  for (const draft of prospectDrafts) {
    await client.drafts.delete(message.inboxId, draft.id);
    console.log(`  Cancelled draft: Step ${draft.metadata.stepNumber}`);
  }

  console.log(`Sequence cancelled for ${prospectEmail}`);

  // Handle the reply (send personalized response, etc.)
  await sendPersonalizedReply(message);
}

async function sendPersonalizedReply(message: any) {
  const response = `Thanks for your reply! I'll reach out with more details shortly.`;

  await client.inboxes.messages.reply(message.inboxId, message.id, {
    body: response,
    clientId: `manual-reply-${message.id}`
  });
}

// Step 4: Bulk sequence launch
async function launchCampaign(inboxId: string, prospects: Prospect[]) {
  console.log(`Launching campaign for ${prospects.length} prospects`);

  const sequences = [];

  for (const prospect of prospects) {
    const sequence = await scheduleSequence(inboxId, prospect);
    sequences.push(sequence);

    // Rate limiting: 1 sequence per 2 seconds
    await new Promise(resolve => setTimeout(resolve, 2000));
  }

  console.log(`\n✅ Campaign launched!`);
  console.log(`Total sequences: ${sequences.length}`);
  console.log(`Total scheduled emails: ${sequences.length * coldSequence.length}`);

  return sequences;
}

// Step 5: Respect send limits
async function checkSendLimits(inboxId: string, prospectCount: number) {
  const capabilities = await client.account.getCapabilities();

  // Find send limit from capabilities
  const sendLimit = capabilities.can.find(c => c.action === 'send_messages');

  if (!sendLimit) {
    throw new Error('Sending not available on current tier');
  }

  const totalEmails = prospectCount * coldSequence.length;

  console.log(`Send limit check:`);
  console.log(`  Total emails in campaign: ${totalEmails}`);
  console.log(`  Current tier: ${capabilities.tier}`);

  // Could check daily/monthly limits here
  // if (totalEmails > dailyLimit) { ... }
}

// Step 6: Campaign analytics
async function getCampaignAnalytics(inboxId: string, sequenceIds: string[]) {
  const stats = {
    sent: 0,
    pending: 0,
    cancelled: 0,
    replied: 0,
    openRate: 0,
    replyRate: 0
  };

  // Get all drafts
  const allDrafts = await client.drafts.list(inboxId);

  for (const sequenceId of sequenceIds) {
    const sequenceDrafts = allDrafts.filter(d =>
      d.metadata?.sequenceId === sequenceId
    );

    stats.pending += sequenceDrafts.filter(d => d.status === 'scheduled').length;
    stats.sent += sequenceDrafts.filter(d => d.status === 'sent').length;
  }

  // Get messages to check for replies
  const messages = await client.inboxes.messages.list(inboxId);
  stats.replied = new Set(messages.map(m => m.from)).size;

  stats.replyRate = stats.sent > 0 ? (stats.replied / stats.sent) * 100 : 0;

  return stats;
}

// Step 7: Pause/resume sequences
async function pauseSequence(inboxId: string, sequenceId: string) {
  const drafts = await client.drafts.list(inboxId, {
    status: 'scheduled'
  });

  const sequenceDrafts = drafts.filter(d =>
    d.metadata?.sequenceId === sequenceId
  );

  for (const draft of sequenceDrafts) {
    // Update to change status or reschedule
    await client.drafts.update(inboxId, draft.id, {
      metadata: {
        ...draft.metadata,
        paused: true
      }
    });
  }

  console.log(`Paused sequence ${sequenceId}`);
}

async function resumeSequence(inboxId: string, sequenceId: string) {
  const drafts = await client.drafts.list(inboxId);

  const sequenceDrafts = drafts.filter(d =>
    d.metadata?.sequenceId === sequenceId &&
    d.metadata?.paused === true
  );

  for (const draft of sequenceDrafts) {
    await client.drafts.update(inboxId, draft.id, {
      metadata: {
        ...draft.metadata,
        paused: false
      }
    });
  }

  console.log(`Resumed sequence ${sequenceId}`);
}

// Complete workflow
async function runColdSequenceCampaign() {
  const inbox = await client.inboxes.create({
    username: 'cold-outreach',
    clientId: 'cold-sequence-v1'
  });

  console.log(`Campaign inbox: ${inbox.address}\n`);

  // Example prospects
  const prospects: Prospect[] = [
    { email: 'john@techcorp.com', name: 'John', company: 'TechCorp' },
    { email: 'sarah@innovate.io', name: 'Sarah', company: 'Innovate.io' },
    { email: 'mike@startup.ai', name: 'Mike', company: 'Startup.AI' }
  ];

  // Check send limits
  await checkSendLimits(inbox.id, prospects.length);

  // Launch campaign
  const sequences = await launchCampaign(inbox.id, prospects);

  // Register webhook for replies
  const authedClient = new DaimonClient({ apiKey: inbox.apiKey });
  await authedClient.webhooks.create({
    endpointUrl: 'https://your-agent.com/webhook',
    events: ['message.received'],
    inboxId: inbox.id
  });

  console.log('\n📊 Campaign Dashboard:');
  console.log('  Monitor replies at http://localhost:3000/analytics');

  // Start webhook server
  app.get('/analytics', async (req, res) => {
    const stats = await getCampaignAnalytics(
      inbox.id,
      sequences.map(s => s.sequenceId)
    );

    res.json(stats);
  });

  app.listen(3000, () => {
    console.log('\n✅ Cold sequence campaign running on port 3000');
  });
}

runColdSequenceCampaign();
from daimon_email import DaimonClient
from flask import Flask, request, jsonify
import os
from datetime import datetime, timedelta
from typing import List, Dict

client = DaimonClient(api_key=os.environ.get('DAIMON_API_KEY'))
app = Flask(__name__)

# Step 1: Define sequence template
def get_sequence_steps():
    return [
        {
            'day_offset': 0,
            'subject': 'Quick question about {company}',
            'body_template': lambda p: f"""Hi {p['name']},

I noticed {p['company']} is in the [industry] space and thought you might be interested in how we're helping similar companies [specific value prop].

Would love to share a quick case study if you're open to it.

Best,
Sales Agent"""
        },
        {
            'day_offset': 3,
            'subject': 'Re: Quick question about {company}',
            'body_template': lambda p: f"""Hi {p['name']},

Following up on my previous email.

I wanted to share a quick win from a company similar to {p['company']} - they saw a 3x improvement in [metric] within 30 days.

Worth a 10-minute call to explore?

Best,
Sales Agent"""
        },
        {
            'day_offset': 7,
            'subject': 'Re: Quick question about {company}',
            'body_template': lambda p: f"""{p['name']},

I know you're busy, so I'll keep this brief.

We have a limited number of Q2 onboarding slots, and I think {p['company']} would be a perfect fit.

If you're interested, I'd love to share more details. Otherwise, I'll assume the timing isn't right.

Best,
Sales Agent"""
        },
        {
            'day_offset': 14,
            'subject': 'Re: Quick question about {company}',
            'body_template': lambda p: f"""{p['name']},

No worries if the timing isn't right!

I'll close this out on my end. Feel free to reach out if anything changes in the future.

Best of luck with {p['company']}!

Sales Agent"""
        }
    ]

# Step 2: Schedule entire sequence as drafts
def schedule_sequence(inbox_id: str, prospect: Dict):
    print(f"Scheduling sequence for {prospect['email']}")

    now = datetime.now()
    draft_ids = []
    sequence_steps = get_sequence_steps()

    for idx, step in enumerate(sequence_steps):
        send_at = now + timedelta(days=step['day_offset'])

        subject = step['subject'].format(company=prospect['company'])
        body = step['body_template'](prospect)

        # Create draft with scheduled send time
        draft = client.drafts.create(inbox_id, {
            'to': prospect['email'],
            'subject': subject,
            'body': body,
            'send_at': send_at.isoformat(),
            'client_id': f"sequence-{prospect['email']}-step-{step['day_offset']}",
            'metadata': {
                'sequence_id': f"sequence-{prospect['email']}",
                'step_number': idx,
                'prospect_email': prospect['email']
            }
        })

        draft_ids.append(draft['id'])

        print(f"  Step {step['day_offset']}: Scheduled for {send_at.isoformat()}")

    return {
        'sequence_id': f"sequence-{prospect['email']}",
        'draft_ids': draft_ids,
        'prospect': prospect
    }

# Step 3: Monitor for replies and cancel sequence
@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} - canceling remaining sequence")

    # Find and cancel all pending drafts for this prospect
    drafts = client.drafts.list(message['inbox_id'], status='scheduled')

    prospect_drafts = [
        d for d in drafts
        if d.get('metadata', {}).get('prospect_email') == prospect_email
        and d['to'] == prospect_email
    ]

    for draft in prospect_drafts:
        client.drafts.delete(message['inbox_id'], draft['id'])
        print(f"  Cancelled draft: Step {draft['metadata']['step_number']}")

    print(f"Sequence cancelled for {prospect_email}")

    # Handle the reply
    send_personalized_reply(message)

def send_personalized_reply(message: Dict):
    response = "Thanks for your reply! I'll reach out with more details shortly."

    client.inboxes.messages.reply(message['inbox_id'], message['id'], {
        'body': response,
        'client_id': f"manual-reply-{message['id']}"
    })

# Step 4: Bulk sequence launch
def launch_campaign(inbox_id: str, prospects: List[Dict]):
    print(f"Launching campaign for {len(prospects)} prospects")

    sequences = []

    for prospect in prospects:
        sequence = schedule_sequence(inbox_id, prospect)
        sequences.append(sequence)

        # Rate limiting
        import time
        time.sleep(2)

    print(f"\n✅ Campaign launched!")
    print(f"Total sequences: {len(sequences)}")
    print(f"Total scheduled emails: {len(sequences) * len(get_sequence_steps())}")

    return sequences

# Step 5: Campaign analytics
def get_campaign_analytics(inbox_id: str, sequence_ids: List[str]):
    stats = {
        'sent': 0,
        'pending': 0,
        'cancelled': 0,
        'replied': 0,
        'open_rate': 0,
        'reply_rate': 0
    }

    # Get all drafts
    all_drafts = client.drafts.list(inbox_id)

    for sequence_id in sequence_ids:
        sequence_drafts = [
            d for d in all_drafts
            if d.get('metadata', {}).get('sequence_id') == sequence_id
        ]

        stats['pending'] += len([d for d in sequence_drafts if d['status'] == 'scheduled'])
        stats['sent'] += len([d for d in sequence_drafts if d['status'] == 'sent'])

    # Get messages to check for replies
    messages = client.inboxes.messages.list(inbox_id)
    stats['replied'] = len(set(m['from'] for m in messages))

    if stats['sent'] > 0:
        stats['reply_rate'] = (stats['replied'] / stats['sent']) * 100

    return stats

# Complete workflow
def run_cold_sequence_campaign():
    inbox = client.inboxes.create(
        username='cold-outreach',
        client_id='cold-sequence-v1'
    )

    print(f"Campaign inbox: {inbox.address}\n")

    # Example prospects
    prospects = [
        {'email': 'john@techcorp.com', 'name': 'John', 'company': 'TechCorp'},
        {'email': 'sarah@innovate.io', 'name': 'Sarah', 'company': 'Innovate.io'},
        {'email': 'mike@startup.ai', 'name': 'Mike', 'company': 'Startup.AI'}
    ]

    # Launch campaign
    sequences = launch_campaign(inbox['id'], prospects)

    # Register webhook
    authed_client = DaimonClient(api_key=inbox['api_key'])
    authed_client.webhooks.create(
        endpoint_url='https://your-agent.com/webhook',
        events=['message.received'],
        inbox_id=inbox['id']
    )

    print('\n📊 Campaign Dashboard:')
    print('  Monitor replies at http://localhost:3000/analytics')

    # Analytics endpoint
    @app.get('/analytics')
    def analytics():
        stats = get_campaign_analytics(
            inbox['id'],
            [s['sequence_id'] for s in sequences]
        )
        return jsonify(stats)

    app.run(port=3000)

if __name__ == '__main__':
    run_cold_sequence_campaign()

Key Features

  • Multi-step automation: Schedule entire sequence upfront
  • Smart cancellation: Stop sequence when prospect replies
  • Rate limiting: Respect send limits and timing rules
  • Analytics: Track campaign performance metrics
  • Idempotency: Prevent duplicate sends with clientId

Best Practices

  1. Start with 3-4 steps: Don't over-email prospects
  2. Space steps appropriately: 3-7 days between emails
  3. Always include opt-out: Make disengagement easy
  4. Personalize every step: Use prospect data in templates
  5. Monitor replies: Cancel sequences immediately on response

Next Steps