Volume 2: Organizational Intelligence Platforms

Pattern 22: Progressive Escalation Sequences

Intent

Design multi-step intervention sequences that begin with gentle, low-pressure touchpoints and progressively increase in directness, urgency, and personal involvement when earlier steps don't produce desired responses, helping people avoid negative outcomes through calibrated, caring persistence rather than giving up after a single attempt.

Also Known As

  • Escalation Ladders
  • Graduated Response Sequences
  • Tiered Intervention Strategies
  • Progressive Outreach Campaigns
  • Stepped Care Approach

Problem

A single intervention attempt rarely works. But how many times? How forceful? When to stop?

The one-and-done failure:

Martinez family showing withdrawal risk (87% probability). Sarah sends one email: "Hi! Just checking in, how are things going?"

No response.

Sarah thinks: "Well, I tried. They didn't respond. Must not want help."

Family withdraws 2 weeks later.

Post-mortem reveals: - Email went to spam folder (family never saw it) - Even if seen, message was too casual (didn't convey urgency) - Family WAS struggling (needed help but didn't know how to ask) - One more attempt - especially a phone call - would have saved them

The opposite problem: Too aggressive too fast:

Johnson family misses one payment (first time ever, probably just forgot).

System immediately: - Sends "PAYMENT OVERDUE" email (scary, formal) - Locks portal access (punitive) - Calls them at work (embarrassing) - Threatens enrollment cancellation (nuclear option)

Family pays but feels attacked. Engagement drops 25 points. Considering leaving.

The problem isn't trying - it's trying wrong:

Too gentle = ineffective: - "Just checking in" → ignored - "Let us know if you need anything" → too vague - "No pressure!" → signals low priority

Too aggressive = damages relationships: - Immediate threats → feels punitive - Public embarrassment → destroys trust - No grace period → no room for life's complexity

What we need: Progressive escalation that's both effective AND caring:

Step 1: Gentle nudge ("Friendly reminder - payment due Friday!") - Light touch, friendly tone - Easy to respond to - No pressure

Step 2: Clearer context ("Hey, noticed payment due - everything OK? Here to help!") - More direct - Offers help - Shows concern

Step 3: Increased urgency ("Payment now 3 days overdue - need to resolve by Friday to avoid holds") - Clear consequences - Specific deadline - Still helpful tone

Step 4: Direct contact (Personal phone call from coordinator) - Human connection - Problem-solving conversation - Last chance before consequences

Step 5: Formal action (Portal hold, formal notice) - Clear that this is serious - Documented process - Path to resolution still offered

Without progressive escalation: - Give up too soon (miss opportunities to help) - Come on too strong (damage relationships) - Inconsistent approach (some people get 10 tries, others get 1) - No learning (don't know which steps work)

With progressive escalation: - Systematic persistence (don't give up prematurely) - Calibrated intensity (match urgency to situation) - Preserve relationships (start gentle, escalate only as needed) - Measurable effectiveness (know which step typically converts) - Fair consistency (everyone gets same sequence)

Context

When this pattern applies:

  • Interventions often fail on first attempt
  • Have legitimate reason to persist (helping, not harassing)
  • Can calibrate message intensity appropriately
  • Willing to invest in multi-touch sequences
  • Want to preserve relationships while being effective

When this pattern may not be needed:

  • Single-touch interventions work fine
  • No room for escalation (immediate action required)
  • Can't differentiate gentle vs strong messaging
  • Legal/compliance prevents multi-contact

Forces

Competing concerns:

1. Persistence vs Harassment - Persistence = caring, helpful, saves relationships - Harassment = annoying, pushy, damages relationships - Balance: Clear value in each touchpoint, respect boundaries

2. Gentle vs Effective - Gentle = preserves relationships but may be ignored - Forceful = gets attention but may damage relationships - Balance: Start gentle, escalate based on response/urgency

3. Speed vs Relationship - Fast escalation = quick resolution but feels aggressive - Slow escalation = relationship-preserving but allows problems to worsen - Balance: Match pace to severity and response

4. Automated vs Personal - Automated = scalable, consistent, but impersonal - Personal = warm, flexible, but doesn't scale - Balance: Automate early steps, personal for later steps

5. Clear vs Vague - Clear = specific asks, but may seem demanding - Vague = friendly but ineffective - Balance: Progressively increase clarity

Solution

Design escalation sequences with:

1. Escalation Dimensions

Multiple ways to "turn up the intensity":

A. Channel Escalation:

Email → SMS → Phone → In-Person
(Low personal investment → High personal investment)

B. Tone Escalation:

Friendly → Concerned → Urgent → Formal
("Just checking!" → "Need to talk" → "Requires immediate attention")

C. Sender Escalation:

Automated System → Coordinator → Director → Board President
(Lower authority → Higher authority)

D. Specificity Escalation:

Vague → Specific → Very Specific
("Checking in" → "Payment due" → "Payment of $450 due by Friday or portal locks")

E. Consequence Escalation:

No consequences → Soft consequences → Hard consequences
(None → "May affect access" → "Portal will lock Friday 5pm")

2. Escalation Pacing

Severity-Based Timing: - Low urgency: 7 days between steps (payment reminder) - Medium urgency: 3 days between steps (engagement drop) - High urgency: 1 day between steps (critical withdrawal risk) - Crisis: Same day (safety concern, immediate issue)

Response-Based Advancement: - Response received → Pause escalation, handle response - No response → Continue to next step - Partial response → Modify sequence (e.g., skip to call)

3. Escalation Paths

Different sequences for different situations:

Payment Reminder Sequence (Low → Medium urgency): 1. Day -3: Friendly reminder (email) 2. Day 0: Payment due today (email + SMS) 3. Day +2: Payment overdue - please resolve (email) 4. Day +5: Urgent - consequences explained (email + SMS) 5. Day +7: Coordinator call (personal) 6. Day +10: Formal notice + portal hold

Engagement Decline Sequence (Medium urgency): 1. Week 0: "We miss you!" (email) 2. Week 2: "Everything OK?" with specific concerns (email) 3. Week 3: "Let's chat" (SMS + coordinator call offer) 4. Week 4: Coordinator call (personal intervention)

Critical Withdrawal Risk (High urgency): 1. Day 0: Immediate email (caring, offers help) 2. Day 1: Follow-up SMS (more direct) 3. Day 2: Coordinator call (personal conversation) 4. Day 3: Director call if needed (escalated authority)

4. De-escalation Paths

Just as important as escalation:

if (responseReceived) {
  // Pause escalation
  pauseSequence();

  // Move to resolution workflow
  startResolutionWorkflow();

  // Record what worked
  logSuccessfulStep(currentStep);
}

5. Escape Valves

Ways for people to stop escalation: - "Unsubscribe from these reminders" (clear opt-out) - "I need more time" option (pause sequence) - "This doesn't apply to me" (remove from sequence) - Explicit response (even "got it, handling it" stops escalation)

Structure

Escalation Sequence Tables

-- Define escalation sequence templates
CREATE TABLE escalation_sequences (
  sequence_id INT PRIMARY KEY IDENTITY(1,1),

  sequence_name VARCHAR(200) NOT NULL,
  description NVARCHAR(1000),

  -- Triggering
  trigger_type VARCHAR(100),  -- 'payment_overdue', 'engagement_decline', 'high_risk'
  urgency_level VARCHAR(50),  -- 'low', 'medium', 'high', 'critical'

  -- Steps (in order)
  steps NVARCHAR(MAX) NOT NULL,  -- JSON array of step definitions

  -- Pacing
  default_step_interval_days INT DEFAULT 3,

  -- De-escalation
  auto_pause_on_response BIT DEFAULT 1,

  active BIT DEFAULT 1,
  created_date DATETIME2 DEFAULT GETDATE()
);

-- Each step in an escalation sequence
CREATE TABLE escalation_steps (
  step_id INT PRIMARY KEY IDENTITY(1,1),
  sequence_id INT NOT NULL,

  step_number INT NOT NULL,  -- Order in sequence (1, 2, 3...)
  step_name VARCHAR(200),

  -- Communication
  channel VARCHAR(50),  -- 'email', 'sms', 'call', 'portal_notification'
  template_id INT,  -- Reference to message template
  tone VARCHAR(50),  -- 'friendly', 'concerned', 'urgent', 'formal'

  -- Sender
  sender_type VARCHAR(100),  -- 'system', 'coordinator', 'director'

  -- Timing
  days_after_previous INT DEFAULT 0,
  time_of_day TIME,  -- Preferred send time (e.g., 9:00 AM)

  -- Consequences mentioned
  mentions_consequences BIT DEFAULT 0,
  consequence_text NVARCHAR(500),

  -- Completion criteria
  success_criteria NVARCHAR(MAX),  -- JSON: what counts as "step successful"

  CONSTRAINT FK_step_sequence FOREIGN KEY (sequence_id)
    REFERENCES escalation_sequences(sequence_id)
);

-- Track active escalation instances
CREATE TABLE escalation_instances (
  instance_id INT PRIMARY KEY IDENTITY(1,1),
  sequence_id INT NOT NULL,
  family_id INT NOT NULL,

  -- State
  status VARCHAR(50) DEFAULT 'active',  -- 'active', 'paused', 'completed', 'cancelled'
  current_step INT DEFAULT 1,

  -- Triggering context
  triggered_by VARCHAR(200),  -- What started this escalation
  trigger_date DATETIME2 DEFAULT GETDATE(),

  -- Execution
  last_step_executed_at DATETIME2,
  next_step_scheduled_at DATETIME2,

  -- Outcome
  resolution_method VARCHAR(100),  -- 'responded_step_2', 'manual_intervention', 'completed_sequence'
  resolved_at DATETIME2,

  -- Tracking
  total_touchpoints INT DEFAULT 0,
  response_received BIT DEFAULT 0,
  response_step INT,  -- Which step got response

  created_date DATETIME2 DEFAULT GETDATE(),

  CONSTRAINT FK_instance_sequence FOREIGN KEY (sequence_id)
    REFERENCES escalation_sequences(sequence_id),
  CONSTRAINT FK_instance_family FOREIGN KEY (family_id)
    REFERENCES families(family_id)
);

-- Log each touchpoint in escalation
CREATE TABLE escalation_touchpoints (
  touchpoint_id INT PRIMARY KEY IDENTITY(1,1),
  instance_id INT NOT NULL,
  step_id INT NOT NULL,

  -- Execution
  sent_at DATETIME2 DEFAULT GETDATE(),
  channel VARCHAR(50),

  -- Content
  subject NVARCHAR(200),
  message_preview NVARCHAR(500),

  -- Delivery
  delivery_status VARCHAR(50),  -- 'sent', 'delivered', 'bounced', 'opened', 'clicked'

  -- Response
  response_received BIT DEFAULT 0,
  response_type VARCHAR(100),  -- 'payment_made', 'replied_email', 'called_back'
  response_date DATETIME2,

  CONSTRAINT FK_touchpoint_instance FOREIGN KEY (instance_id)
    REFERENCES escalation_instances(instance_id)
);

-- Track effectiveness of each step
CREATE TABLE escalation_step_effectiveness (
  effectiveness_id INT PRIMARY KEY IDENTITY(1,1),

  sequence_id INT NOT NULL,
  step_number INT NOT NULL,

  -- Performance metrics
  times_sent INT DEFAULT 0,
  times_responded INT DEFAULT 0,
  response_rate DECIMAL(5,2),  -- Percentage

  avg_response_time_hours DECIMAL(8,2),

  -- Last updated
  calculation_date DATE,

  CONSTRAINT FK_effectiveness_sequence FOREIGN KEY (sequence_id)
    REFERENCES escalation_sequences(sequence_id)
);

Implementation

Escalation Manager

class EscalationManager {
  constructor(db) {
    this.db = db;
  }

  async startEscalation(familyId, sequenceName, triggeredBy) {
    // Get sequence definition
    const sequence = await this.db.query(`
      SELECT * FROM escalation_sequences 
      WHERE sequence_name = ? AND active = 1
    `, [sequenceName]);

    if (!sequence.length) {
      throw new Error(`Sequence not found: ${sequenceName}`);
    }

    const seq = sequence[0];

    // Check if already in this escalation
    const existing = await this.db.query(`
      SELECT instance_id FROM escalation_instances
      WHERE family_id = ? 
        AND sequence_id = ?
        AND status = 'active'
    `, [familyId, seq.sequence_id]);

    if (existing.length > 0) {
      console.log(`Family ${familyId} already in escalation ${sequenceName}`);
      return existing[0].instance_id;
    }

    // Create new instance
    const result = await this.db.query(`
      INSERT INTO escalation_instances (
        sequence_id,
        family_id,
        triggered_by,
        current_step,
        next_step_scheduled_at
      )
      OUTPUT INSERTED.instance_id
      VALUES (?, ?, ?, 1, GETDATE())
    `, [seq.sequence_id, familyId, triggeredBy]);

    const instanceId = result[0].instance_id;

    console.log(`Started escalation ${sequenceName} for family ${familyId} (instance ${instanceId})`);

    return instanceId;
  }

  async executeNextStep(instanceId) {
    // Get instance details
    const instance = await this.db.query(`
      SELECT 
        ei.*,
        es.sequence_name,
        es.steps
      FROM escalation_instances ei
      JOIN escalation_sequences es ON ei.sequence_id = es.sequence_id
      WHERE ei.instance_id = ?
    `, [instanceId]);

    if (!instance.length) {
      throw new Error(`Instance not found: ${instanceId}`);
    }

    const inst = instance[0];

    // Parse steps
    const steps = JSON.parse(inst.steps);
    const stepIndex = inst.current_step - 1;  // Convert to 0-based

    if (stepIndex >= steps.length) {
      // Escalation complete
      await this.completeEscalation(instanceId, 'completed_sequence');
      return { complete: true };
    }

    const step = steps[stepIndex];

    // Get family context
    const family = await this.getFamilyContext(inst.family_id);

    // Execute step based on channel
    let result;
    switch (step.channel) {
      case 'email':
        result = await this.sendEmail(step, family, inst);
        break;
      case 'sms':
        result = await this.sendSMS(step, family, inst);
        break;
      case 'call':
        result = await this.scheduleCall(step, family, inst);
        break;
      case 'portal_notification':
        result = await this.sendPortalNotification(step, family, inst);
        break;
      default:
        throw new Error(`Unknown channel: ${step.channel}`);
    }

    // Log touchpoint
    await this.logTouchpoint(instanceId, step, result);

    // Update instance
    const nextStepSchedule = this.calculateNextStepTime(step, steps, stepIndex + 1);

    await this.db.query(`
      UPDATE escalation_instances
      SET 
        current_step = current_step + 1,
        last_step_executed_at = GETDATE(),
        next_step_scheduled_at = ?,
        total_touchpoints = total_touchpoints + 1
      WHERE instance_id = ?
    `, [nextStepSchedule, instanceId]);

    return { success: true, step: step.name, nextScheduled: nextStepSchedule };
  }

  async sendEmail(step, family, instance) {
    // Get template with appropriate tone
    const template = await this.getTemplate(step.template_id, step.tone);

    // Render with family context
    const subject = this.renderTemplate(template.subject, family, instance);
    const body = this.renderTemplate(template.body, family, instance);

    // Send via email service
    const emailService = require('./email-service');
    const result = await emailService.send({
      to: family.email,
      from: this.getSenderEmail(step.sender_type),
      subject: subject,
      html: body
    });

    return {
      channel: 'email',
      delivered: result.accepted && result.accepted.length > 0,
      subject: subject,
      preview: body.substring(0, 200)
    };
  }

  async sendSMS(step, family, instance) {
    const template = await this.getTemplate(step.template_id, step.tone);
    const message = this.renderTemplate(template.sms_text, family, instance);

    const smsService = require('./sms-service');
    const result = await smsService.send({
      to: family.phone,
      message: message
    });

    return {
      channel: 'sms',
      delivered: result.status === 'sent',
      preview: message
    };
  }

  async scheduleCall(step, family, instance) {
    // Create task for coordinator
    await this.db.query(`
      INSERT INTO coordinator_tasks (
        family_id,
        task_type,
        description,
        priority,
        due_date,
        context_data
      ) VALUES (?, 'escalation_call', ?, 'high', DATEADD(DAY, 1, GETDATE()), ?)
    `, [
      family.family_id,
      `Escalation: ${step.name} - ${instance.sequence_name}`,
      JSON.stringify({
        escalation_instance_id: instance.instance_id,
        step_number: instance.current_step,
        talking_points: step.talking_points
      })
    ]);

    return {
      channel: 'call',
      delivered: true,
      preview: `Call task created: ${step.name}`
    };
  }

  async sendPortalNotification(step, family, instance) {
    const template = await this.getTemplate(step.template_id, step.tone);
    const message = this.renderTemplate(template.notification_text, family, instance);

    await this.db.query(`
      INSERT INTO portal_notifications (
        family_id,
        notification_type,
        title,
        message,
        priority,
        read_status
      ) VALUES (?, 'escalation', ?, ?, ?, 0)
    `, [family.family_id, step.name, message, step.tone === 'urgent' ? 'high' : 'normal']);

    return {
      channel: 'portal_notification',
      delivered: true,
      preview: message
    };
  }

  async getTemplate(templateId, tone) {
    // Get base template and adjust for tone
    const template = await this.db.query(`
      SELECT * FROM message_templates WHERE template_id = ?
    `, [templateId]);

    if (!template.length) {
      throw new Error(`Template not found: ${templateId}`);
    }

    return template[0];
  }

  getSenderEmail(senderType) {
    const senders = {
      'system': 'noreply@homeschoolcoop.org',
      'coordinator': 'sarah@homeschoolcoop.org',
      'director': 'director@homeschoolcoop.org'
    };

    return senders[senderType] || senders['system'];
  }

  renderTemplate(template, family, instance) {
    // Simple variable substitution
    return template
      .replace(/\{\{family_name\}\}/g, family.family_name)
      .replace(/\{\{parent_first_name\}\}/g, family.parent_first_name)
      .replace(/\{\{coordinator_name\}\}/g, 'Sarah')
      .replace(/\{\{days_overdue\}\}/g, this.calculateDaysOverdue(instance))
      .replace(/\{\{amount_due\}\}/g, family.amount_due || '$450');
  }

  calculateDaysOverdue(instance) {
    const triggerDate = new Date(instance.trigger_date);
    const now = new Date();
    return Math.floor((now - triggerDate) / (1000 * 60 * 60 * 24));
  }

  calculateNextStepTime(currentStep, allSteps, nextStepIndex) {
    if (nextStepIndex >= allSteps.length) {
      return null;  // No more steps
    }

    const nextStep = allSteps[nextStepIndex];
    const daysDelay = nextStep.days_after_previous || 0;

    const nextTime = new Date();
    nextTime.setDate(nextTime.getDate() + daysDelay);

    // Set to preferred time of day
    if (nextStep.time_of_day) {
      const [hours, minutes] = nextStep.time_of_day.split(':');
      nextTime.setHours(parseInt(hours), parseInt(minutes), 0, 0);
    } else {
      // Default to 9 AM
      nextTime.setHours(9, 0, 0, 0);
    }

    return nextTime;
  }

  async logTouchpoint(instanceId, step, result) {
    await this.db.query(`
      INSERT INTO escalation_touchpoints (
        instance_id,
        step_id,
        channel,
        subject,
        message_preview,
        delivery_status
      ) VALUES (?, ?, ?, ?, ?, ?)
    `, [
      instanceId,
      step.step_id || 0,
      result.channel,
      result.subject || '',
      result.preview,
      result.delivered ? 'delivered' : 'failed'
    ]);
  }

  async recordResponse(instanceId, responseType) {
    // Mark that response received
    await this.db.query(`
      UPDATE escalation_instances
      SET 
        status = 'paused',
        response_received = 1,
        response_step = current_step,
        resolved_at = GETDATE(),
        resolution_method = ?
      WHERE instance_id = ?
    `, [`responded_${responseType}`, instanceId]);

    // Update effectiveness tracking
    const instance = await this.db.query(`
      SELECT sequence_id, current_step FROM escalation_instances WHERE instance_id = ?
    `, [instanceId]);

    if (instance.length > 0) {
      await this.updateStepEffectiveness(
        instance[0].sequence_id,
        instance[0].current_step,
        true  // response received
      );
    }

    console.log(`Escalation ${instanceId} paused - response received at step ${instance[0].current_step}`);
  }

  async updateStepEffectiveness(sequenceId, stepNumber, responseReceived) {
    await this.db.query(`
      MERGE INTO escalation_step_effectiveness AS target
      USING (SELECT ? AS sequence_id, ? AS step_number) AS source
      ON (target.sequence_id = source.sequence_id AND target.step_number = source.step_number)
      WHEN MATCHED THEN
        UPDATE SET
          times_sent = times_sent + 1,
          times_responded = times_responded + (CASE WHEN ? = 1 THEN 1 ELSE 0 END),
          response_rate = (CAST(times_responded AS FLOAT) / times_sent) * 100,
          calculation_date = CAST(GETDATE() AS DATE)
      WHEN NOT MATCHED THEN
        INSERT (sequence_id, step_number, times_sent, times_responded, response_rate, calculation_date)
        VALUES (?, ?, 1, ?, ?, CAST(GETDATE() AS DATE));
    `, [
      sequenceId, stepNumber, responseReceived ? 1 : 0,
      sequenceId, stepNumber, responseReceived ? 1 : 0, responseReceived ? 100 : 0
    ]);
  }

  async completeEscalation(instanceId, resolutionMethod) {
    await this.db.query(`
      UPDATE escalation_instances
      SET 
        status = 'completed',
        resolved_at = GETDATE(),
        resolution_method = ?
      WHERE instance_id = ?
    `, [resolutionMethod, instanceId]);

    console.log(`Escalation ${instanceId} completed: ${resolutionMethod}`);
  }

  async cancelEscalation(instanceId, reason) {
    await this.db.query(`
      UPDATE escalation_instances
      SET 
        status = 'cancelled',
        resolved_at = GETDATE(),
        resolution_method = ?
      WHERE instance_id = ?
    `, [`cancelled_${reason}`, instanceId]);

    console.log(`Escalation ${instanceId} cancelled: ${reason}`);
  }

  async getFamilyContext(familyId) {
    const family = await this.db.query(`
      SELECT 
        f.*,
        fem.engagement_score,
        ra.withdrawal_risk
      FROM families f
      LEFT JOIN family_engagement_metrics fem ON f.family_id = fem.family_id
      LEFT JOIN risk_assessments ra ON f.family_id = ra.family_id
      WHERE f.family_id = ?
    `, [familyId]);

    return family[0];
  }
}

module.exports = EscalationManager;

Usage Example

// Define payment reminder escalation sequence
await db.query(`
  INSERT INTO escalation_sequences (
    sequence_name,
    description,
    trigger_type,
    urgency_level,
    steps
  ) VALUES (
    'payment_reminder',
    'Progressive payment reminder sequence',
    'payment_overdue',
    'medium',
    ?
  )
`, [JSON.stringify([
  {
    step_id: 1,
    name: 'Friendly Reminder',
    channel: 'email',
    template_id: 101,
    tone: 'friendly',
    sender_type: 'system',
    days_after_previous: 0,
    time_of_day: '09:00',
    mentions_consequences: false,
    talking_points: ['Payment due soon', 'Easy payment options']
  },
  {
    step_id: 2,
    name: 'Payment Due Notice',
    channel: 'email',
    template_id: 102,
    tone: 'concerned',
    sender_type: 'coordinator',
    days_after_previous: 3,
    time_of_day: '10:00',
    mentions_consequences: false,
    talking_points: ['Payment now due', 'Let us know if you need help']
  },
  {
    step_id: 3,
    name: 'Overdue Notice + SMS',
    channel: 'email',
    template_id: 103,
    tone: 'urgent',
    sender_type: 'coordinator',
    days_after_previous: 2,
    time_of_day: '09:00',
    mentions_consequences: true,
    consequence_text: 'Portal access may be limited if not resolved by Friday',
    talking_points: ['Payment overdue', 'Consequences explained', 'Payment plan available']
  },
  {
    step_id: 4,
    name: 'Personal SMS Follow-up',
    channel: 'sms',
    template_id: 104,
    tone: 'urgent',
    sender_type: 'coordinator',
    days_after_previous: 2,
    time_of_day: '14:00',
    mentions_consequences: true,
    talking_points: ['Need to resolve ASAP']
  },
  {
    step_id: 5,
    name: 'Coordinator Call',
    channel: 'call',
    template_id: 105,
    tone: 'formal',
    sender_type: 'coordinator',
    days_after_previous: 2,
    time_of_day: '10:00',
    mentions_consequences: true,
    talking_points: ['Personal conversation', 'Work out payment plan', 'Last step before portal hold']
  },
  {
    step_id: 6,
    name: 'Formal Notice + Portal Hold',
    channel: 'email',
    template_id: 106,
    tone: 'formal',
    sender_type: 'director',
    days_after_previous: 3,
    time_of_day: '09:00',
    mentions_consequences: true,
    consequence_text: 'Portal access suspended. Payment required to restore.',
    talking_points: ['Final notice', 'Portal suspended', 'Path to resolution']
  }
])]);

// Start escalation when payment becomes overdue
const manager = new EscalationManager(db);
const instanceId = await manager.startEscalation(
  familyId: 123,
  sequenceName: 'payment_reminder',
  triggeredBy: 'payment_overdue_3_days'
);

// System automatically executes steps:
// Day 0: Friendly email
// Day 3: Concerned email from coordinator
// Day 5: Urgent email + SMS (mentions consequences)
// Day 7: SMS follow-up
// Day 9: Coordinator call scheduled
// Day 12: Formal notice + portal hold

// If family pays at Day 6:
await manager.recordResponse(instanceId, 'payment_received');
// → Escalation pauses, family restored to good standing
// → System learns: Step 3 (urgent email) was effective

Variations

By Escalation Strategy

Linear Escalation: - Fixed sequence, everyone gets same steps - Simple, predictable - May over/under-escalate for some

Branching Escalation: - Different paths based on response/behavior - Personalized approach - More complex to design

Adaptive Escalation: - ML predicts which step will work - Skip ineffective steps - Requires data, sophisticated

By Pacing

Rapid Escalation: - Days between steps - For urgent situations - Risk: feels aggressive

Gradual Escalation: - Weeks between steps - For low-urgency situations - Risk: problem worsens while waiting

Variable Pacing: - Faster for high-risk families - Slower for low-risk - Matches urgency to situation

By Tone Progression

Gentle → Firm: - Start friendly, end formal - Most common approach - Preserves relationships early

Concerned Throughout: - Maintain caring tone at all steps - Vary specificity, not warmth - For sensitive situations

Professional Consistent: - Same tone throughout - Vary urgency through content - For business contexts

Consequences

Benefits

1. Higher response rates Single email: 15% response. 5-step sequence: 68% response.

2. Earlier intervention Most responses at step 2-3 (before consequences needed).

3. Relationship preservation Start gentle - family doesn't feel attacked.

4. Systematic persistence Nobody forgotten or given up on prematurely.

5. Learning what works Track which steps convert (optimize sequence).

6. Fair consistency Everyone gets same opportunities to respond.

7. Documented attempts Complete audit trail of escalation.

Costs

1. More complex than single-touch Design, implement, manage sequences.

2. Longer time to resolution 6-step sequence takes 2+ weeks.

3. More touchpoints Higher communication volume.

4. Requires monitoring Need to track responses, pause appropriately.

5. Can feel pushy if poorly designed Too many steps = harassment.

6. Template maintenance Multiple messages per sequence to maintain.

Sample Code

Analyze escalation effectiveness:

async function analyzeEscalationEffectiveness(sequenceName) {
  const stats = await db.query(`
    SELECT 
      ei.response_step,
      COUNT(*) as families,
      AVG(ei.total_touchpoints) as avg_touchpoints,
      AVG(DATEDIFF(DAY, ei.trigger_date, ei.resolved_at)) as avg_days_to_resolve
    FROM escalation_instances ei
    JOIN escalation_sequences es ON ei.sequence_id = es.sequence_id
    WHERE es.sequence_name = ?
      AND ei.response_received = 1
    GROUP BY ei.response_step
    ORDER BY ei.response_step
  `, [sequenceName]);

  console.log(`\nEscalation Effectiveness: ${sequenceName}\n`);
  stats.forEach(s => {
    console.log(`Step ${s.response_step}: ${s.families} families (${s.avg_days_to_resolve.toFixed(1)} days avg)`);
  });

  return stats;
}

Known Uses

Homeschool Co-op Intelligence Platform - Payment reminders: 6-step sequence, 68% response rate (vs 15% single email) - Engagement recovery: 4-step sequence, 52% re-engagement - Most responses at step 2-3 (before consequences)

Dunning Management (SaaS) - Stripe: 4-step payment recovery sequences - Chargebee: Customizable escalation paths - 70-80% recovery rates with progressive dunning

Collections Industry - Regulatory limits on contact frequency - Required escalation documentation - Consumer protection compliance

Healthcare - Appointment reminders (progressively urgent) - Medication adherence (caring escalation) - Preventive care follow-up

Requires: - Pattern 21: Automated Workflow Execution - execution engine - Pattern 24: Template-Based Communication - message templates for each step

Enables: - Pattern 23: Triggered Interventions - what triggers escalations - Pattern 26: Feedback Loop Implementation - learn what works

Enhanced by: - Pattern 13: Confidence Scoring - escalate faster for high-confidence predictions - Pattern 25: Multi-Channel Orchestration - coordinate channels in sequence

References

Academic Foundations

  • Cialdini, Robert B. (2006). Influence: The Psychology of Persuasion (Revised ed.). Harper Business. ISBN: 978-0061241895 - Principle of commitment and consistency
  • Fogg, BJ (2002). Persuasive Technology: Using Computers to Change What We Think and Do. Morgan Kaufmann. ISBN: 978-1558606432 - Behavior change through technology
  • Thaler, Richard H., and Cass R. Sunstein (2009). Nudge: Improving Decisions About Health, Wealth, and Happiness. Penguin. ISBN: 978-0143115762 - Choice architecture
  • Kahneman, Daniel (2011). Thinking, Fast and Slow. Farrar, Straus and Giroux. ISBN: 978-0374533557 - Behavioral economics

Behavioral Design

Regulatory Compliance

Practical Implementation

  • Pattern 9: Early Warning Signals - Trigger escalation sequences
  • Pattern 15: Intervention Recommendation Engine - Recommend escalation level
  • Pattern 21: Automated Workflow Execution - Execute escalation workflows
  • Pattern 23: Triggered Interventions - Escalation triggers interventions
  • Volume 3, Pattern 5: Error as Collaboration - Supportive escalation messaging

Tools & Services