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
Related Patterns
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
- Hooked Model: Eyal, N. (2014). Hooked: How to Build Habit-Forming Products. Portfolio. ISBN: 978-0241184837
- BJ Fogg Behavior Model: https://behaviormodel.org/ - B = MAP (Motivation, Ability, Prompt)
- Nudge Database: https://www.stir.ac.uk/media/stirling/services/psychology/nudgedb/Home.html - Evidence-based nudges
Regulatory Compliance
- FDCPA: https://www.ftc.gov/enforcement/statutes/fair-debt-collection-practices-act - Fair Debt Collection Practices Act
- TCPA: https://www.fcc.gov/consumers/guides/telephone-consumer-protection-act - Telephone Consumer Protection Act
- GDPR Article 21: https://gdpr-info.eu/art-21-gdpr/ - Right to object to automated decisions
- Dunning Management Best Practices: https://www.zuora.com/guides/dunning-management/ - SaaS billing escalation
Practical Implementation
- Stripe Dunning: https://stripe.com/docs/billing/revenue-recovery/dunning - Failed payment recovery
- Recurly Dunning: https://recurly.com/research/dunning-management/ - Subscription retention
- ChurnBuster: https://churnbuster.io/ - Failed payment recovery automation
Related Trilogy Patterns
- 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
- Intercom: https://www.intercom.com/ - Progressive customer engagement
- Customer.io: https://customer.io/ - Behavioral messaging campaigns
- Braze: https://www.braze.com/ - Multi-channel engagement escalation
- Iterable: https://iterable.com/ - Cross-channel marketing orchestration