DEE Docs
Guides

Multi-Channel Sequences

Building outreach sequences that coordinate email and voice touchpoints.

Overview

The most common use case for the DEE is a multi-channel outreach sequence: send an email, wait for engagement, follow up via call if no response. The engine coordinates these touchpoints deterministically.

Example: Sales Outreach Sequence

const plan = createPlan({
  id: 'sales-outreach-v2',
  name: 'Sales Outreach Sequence',
  steps: [
    // Day 0: Initial email
    steps.sendEmail({
      id: 'initial-email',
      templateId: 'tmpl_cold_intro',
      subject: '{{product}} for {{contact.company}}',
    }),

    // Day 0 → Day 3: Wait for engagement
    steps.wait({ id: 'wait-1', duration: { days: 3 } }),

    // Day 3: Check if they engaged
    steps.branch({
      id: 'check-email-1',
      conditions: [
        { when: 'events.hasReply("initial-email")', goto: 'end-replied' },
        { when: 'events.hasOpen("initial-email")', goto: 'warm-follow-up' },
        { when: 'default', goto: 'cold-follow-up' },
      ],
    }),

    // Warm path: they opened but didn't reply
    steps.sendEmail({
      id: 'warm-follow-up',
      templateId: 'tmpl_warm_follow_up',
      subject: 'Re: {{product}} for {{contact.company}}',
    }),
    steps.wait({ id: 'wait-2', duration: { days: 2 } }),
    steps.branch({
      id: 'check-email-2',
      conditions: [
        { when: 'events.hasReply("warm-follow-up")', goto: 'end-replied' },
        { when: 'default', goto: 'call-attempt' },
      ],
    }),

    // Cold path: no engagement at all
    steps.sendEmail({
      id: 'cold-follow-up',
      templateId: 'tmpl_cold_follow_up',
      subject: 'Trying to connect, {{contact.firstName}}',
    }),
    steps.wait({ id: 'wait-3', duration: { days: 4 } }),

    // Day 7-9: Call attempt
    steps.sendVoice({
      id: 'call-attempt',
      scriptId: 'script_intro_call',
      voicemail: { detect: true, messageId: 'vm_intro' },
    }),
    steps.wait({ id: 'wait-4', duration: { days: 2 } }),

    // Final email
    steps.sendEmail({
      id: 'breakup-email',
      templateId: 'tmpl_breakup',
      subject: 'Should I close your file?',
    }),
    steps.end({ id: 'end-no-reply', outcome: 'no_response' }),

    // Success endpoint
    steps.end({ id: 'end-replied', outcome: 'replied' }),
  ],

  transitions: transitions.explicit([
    { from: 'initial-email', to: 'wait-1' },
    { from: 'wait-1', to: 'check-email-1' },
    // Warm path
    { from: 'warm-follow-up', to: 'wait-2' },
    { from: 'wait-2', to: 'check-email-2' },
    // Cold path
    { from: 'cold-follow-up', to: 'wait-3' },
    { from: 'wait-3', to: 'call-attempt' },
    // Converge to call
    { from: 'call-attempt', to: 'wait-4' },
    { from: 'wait-4', to: 'breakup-email' },
    { from: 'breakup-email', to: 'end-no-reply' },
  ]),
});

Execution Timeline

For a contact who opens the initial email but never replies:

Day 0  │ initial-email    → delivered
Day 3  │ check-email-1    → branch: warm-follow-up (opened)
Day 3  │ warm-follow-up   → delivered
Day 5  │ check-email-2    → branch: call-attempt (no reply)
Day 5  │ call-attempt     → answered, no commitment
Day 7  │ breakup-email    → delivered
Day 7  │ end-no-reply     → completed (no_response)

Every step, branch decision, and outcome is recorded in the audit log with the exact logical tick it occurred.

Global Exit Conditions

You can define conditions that immediately end the sequence regardless of current step:

const plan = createPlan({
  // ...steps and transitions...
  exitConditions: [
    { when: 'contact.unsubscribed', outcome: 'unsubscribed' },
    { when: 'contact.bounced', outcome: 'undeliverable' },
    { when: 'events.hasReply(any)', outcome: 'replied' },
  ],
});

Exit conditions are evaluated before each step executes. If matched, the run ends immediately and the exit outcome is recorded.

On this page