Pattern 23: Triggered Interventions
Intent
Establish event-driven intervention system that instantly detects critical conditions and automatically initiates appropriate responses with millisecond-to-minute latency, preventing disasters through early detection and immediate action rather than waiting for scheduled checks or human discovery.
Also Known As
- Event-Driven Interventions
- Real-Time Response System
- Trigger-Action Rules
- Conditional Automation
- Alert-Based Interventions
Problem
Waiting for scheduled checks means disasters happen in the gap.
The polling problem:
Current approach: System checks families every night at midnight - Midnight check: Martinez family engagement score = 72 (OK) - 3am: Martinez family's payment bounces (bank account overdrawn) - 8am: Martinez family tries portal login, gets "payment failed" error, feels embarrassed - 9am: Martinez family gets angry, starts looking at other co-ops - 11:59pm: Next scheduled check runs, detects problem - 24 hours too late - family already decided to leave
The human discovery problem:
Sarah manually reviews dashboard weekly - Monday: Checks reports, everything looks fine - Tuesday: Johnson family's email starts bouncing (changed address, didn't update) - Wednesday-Friday: System sends 3 more emails, all bounce - Next Monday: Sarah notices 4 bounced emails - 7 days too late - missed critical communication window
The batch processing problem:
System generates "at-risk" list weekly - Week 1: Chen family engagement = 68 (not flagged) - Tuesday Week 2: Chen family engagement drops to 42 (sudden decline) - Sunday Week 2: Weekly report runs, flags Chen family - 5 days too late - problem festered unnoticed
What happens in the gap: - Payment issues escalate (overdraft fees, angry parents) - Communication breaks down (bounced emails go unnoticed) - Engagement crashes (sudden drops missed) - Opportunities lost (positive moments not celebrated) - Families make decisions (choose to leave before we respond)
The fundamental problem: Time matters
For negative events: - Payment bounced → Need response within HOURS (before embarrassment → anger) - Email bounce → Need response within HOURS (before missing critical messages) - Engagement cliff → Need response within DAYS (before family disengages completely)
For positive events: - First volunteer signup → Need response within MINUTES (strike while iron is hot!) - Referral made → Need response within HOURS (thank them immediately!) - Payment early → Need response within HOURS (positive reinforcement!)
What we need: Event-driven architecture
Instead of "check everything periodically," use "react instantly to events":
Event happens → Trigger evaluates → Intervention executes
(milliseconds to minutes, not hours to days)
Examples:
WHEN payment_bounce DETECTED
→ IMMEDIATELY send friendly "heads up" email
→ CREATE urgent task for coordinator
→ PAUSE any aggressive collection messages
→ WITHIN 1 hour of bounce
WHEN engagement_drops_30_points_in_7_days DETECTED
→ IMMEDIATELY flag for coordinator
→ SEND caring "we miss you" message
→ SCHEDULE call for tomorrow
→ WITHIN 15 minutes of detection
WHEN first_volunteer_signup DETECTED
→ IMMEDIATELY send thank you email
→ NOTIFY coordinator (for personal follow-up)
→ ADD to "active volunteers" cohort
→ WITHIN 5 minutes of signup
Without triggered interventions: - React in hours/days instead of seconds/minutes - Miss critical windows (anger, disengagement, excitement) - Discover problems after damage done - Can't respond to positive events (no positive reinforcement)
With triggered interventions: - React in seconds/minutes (real-time) - Catch problems at inception (before escalation) - Capitalize on positive moments (immediate reinforcement) - Prevent disasters through early intervention
Context
When this pattern applies:
- Time-sensitive situations exist (minutes/hours matter)
- Events can be detected as they occur
- Appropriate interventions can be defined per event type
- System can act autonomously (with oversight)
- Real-time/near-real-time response adds value
When this pattern may not be needed:
- Batch processing acceptable (daily/weekly fine)
- No time-sensitive events
- All interventions require human judgment
- Event detection infrastructure not available
Forces
Competing concerns:
1. Immediate vs Thoughtful - Immediate = fast response, prevent escalation - Thoughtful = consider context, avoid over-reaction - Balance: Immediate for critical, delayed for complex
2. Automated vs Human - Automated = instant, scalable, consistent - Human = judgment, empathy, flexibility - Balance: Automate simple, alert human for complex
3. Sensitive vs Robust - Sensitive = catch everything (false positives) - Robust = only true issues (miss some) - Balance: Tune thresholds per trigger type
4. Simple vs Complex Triggers - Simple = single event (payment bounced) - Complex = patterns (3 emails bounced in 7 days) - Balance: Both - simple for speed, complex for accuracy
5. Proactive vs Reactive - Proactive = predict and prevent - Reactive = respond after event - Balance: Both - predict with ML, respond to actuals
Solution
Build comprehensive trigger-action system with:
1. Trigger Types
A. Single Event Triggers:
WHEN payment_bounced
WHEN email_bounced
WHEN login_failed_3_times
WHEN volunteer_signup
B. Threshold Triggers:
WHEN engagement_score < 40
WHEN days_since_last_login > 30
WHEN bounced_email_count >= 3
C. Pattern Triggers:
WHEN engagement_drops_30_points_in_7_days
WHEN 3_consecutive_payments_late
WHEN no_communication_in_21_days
D. Combination Triggers:
WHEN (engagement_score < 50) AND (payment_overdue) AND (no_login_14_days)
// Multiple conditions must be true
E. Predictive Triggers:
WHEN ml_prediction(withdrawal_risk) > 0.85 AND confidence > 80
// From Pattern 12 + 13
2. Intervention Matching
TriggerCondition → EvaluateContext → SelectIntervention → Execute
3. Priority Levels
- P1 Critical: Execute immediately (seconds)
-
Payment bounced, safety concern, system error
-
P2 High: Execute within minutes
-
Email bounced, engagement cliff, communication breakdown
-
P3 Medium: Execute within hours
-
Low engagement, inactive, approaching threshold
-
P4 Low: Execute within day
- Positive reinforcement, gentle nudges
4. Deduplication
Prevent same trigger firing repeatedly:
// Don't send "payment bounced" email 10 times if payment bounces 10 times
if (sentSameInterventionInLast24Hours) {
skip();
}
5. Context Awareness
Same trigger, different intervention based on context:
WHEN payment_bounced
IF first_time_ever → Send friendly "heads up"
IF happened_before_paid_quickly → Send reminder
IF chronic_payment_issues → Create urgent coordinator task
Structure
Trigger System Tables
-- Define trigger rules
CREATE TABLE intervention_triggers (
trigger_id INT PRIMARY KEY IDENTITY(1,1),
trigger_name VARCHAR(200) NOT NULL,
description NVARCHAR(1000),
-- Trigger type
trigger_type VARCHAR(100), -- 'single_event', 'threshold', 'pattern', 'combination', 'predictive'
-- Condition
condition_definition NVARCHAR(MAX) NOT NULL, -- JSON or SQL expression
-- Evaluation
evaluation_frequency VARCHAR(50), -- 'real_time', 'every_minute', 'every_5_minutes', 'hourly'
-- Priority
priority INT DEFAULT 3, -- 1=critical, 2=high, 3=medium, 4=low
-- Deduplication
dedupe_window_hours INT DEFAULT 24,
-- Intervention
intervention_action_type VARCHAR(100), -- 'email', 'sms', 'task', 'workflow', 'alert'
intervention_template_id INT,
intervention_workflow_id INT,
-- Control
active BIT DEFAULT 1,
created_date DATETIME2 DEFAULT GETDATE(),
CONSTRAINT UQ_trigger_name UNIQUE (trigger_name)
);
-- Track trigger evaluations
CREATE TABLE trigger_evaluations (
evaluation_id INT PRIMARY KEY IDENTITY(1,1),
trigger_id INT NOT NULL,
evaluated_at DATETIME2 DEFAULT GETDATE(),
-- Result
condition_met BIT NOT NULL,
entity_id INT, -- family_id or other entity
-- Context
trigger_context NVARCHAR(MAX), -- JSON: relevant data at time of trigger
-- Action taken
intervention_executed BIT DEFAULT 0,
intervention_skipped_reason NVARCHAR(500),
CONSTRAINT FK_evaluation_trigger FOREIGN KEY (trigger_id)
REFERENCES intervention_triggers(trigger_id)
);
-- Log executed interventions
CREATE TABLE triggered_interventions (
intervention_id INT PRIMARY KEY IDENTITY(1,1),
trigger_id INT NOT NULL,
evaluation_id INT NOT NULL,
family_id INT NOT NULL,
-- Execution
triggered_at DATETIME2 DEFAULT GETDATE(),
executed_at DATETIME2,
-- Action
action_type VARCHAR(100),
action_details NVARCHAR(MAX), -- JSON
-- Result
execution_status VARCHAR(50), -- 'pending', 'executing', 'completed', 'failed'
execution_result NVARCHAR(MAX),
-- Deduplication tracking
dedupe_key VARCHAR(500), -- Combination of trigger + family + timeframe
CONSTRAINT FK_intervention_trigger FOREIGN KEY (trigger_id)
REFERENCES intervention_triggers(trigger_id),
CONSTRAINT FK_intervention_family FOREIGN KEY (family_id)
REFERENCES families(family_id)
);
-- Index for deduplication checks
CREATE INDEX IX_intervention_dedupe ON triggered_interventions(dedupe_key, triggered_at);
-- Track trigger effectiveness
CREATE TABLE trigger_effectiveness (
effectiveness_id INT PRIMARY KEY IDENTITY(1,1),
trigger_id INT NOT NULL,
-- Performance
times_triggered INT DEFAULT 0,
times_executed INT DEFAULT 0,
times_successful INT DEFAULT 0, -- Resulted in desired outcome
success_rate DECIMAL(5,2),
-- Timing
avg_response_time_minutes INT,
calculation_date DATE,
CONSTRAINT FK_effectiveness_trigger FOREIGN KEY (trigger_id)
REFERENCES intervention_triggers(trigger_id)
);
Implementation
Trigger Engine
class TriggerEngine {
constructor(db, eventBus) {
this.db = db;
this.eventBus = eventBus; // Event emitter for real-time events
this.activeTriggers = new Map();
}
async start() {
console.log('Trigger Engine starting...');
// Load all active triggers
await this.loadTriggers();
// Subscribe to real-time events
this.subscribeToEvents();
// Start periodic evaluation for non-real-time triggers
this.startPeriodicEvaluation();
}
async loadTriggers() {
const triggers = await this.db.query(`
SELECT * FROM intervention_triggers WHERE active = 1
`);
for (const trigger of triggers) {
this.activeTriggers.set(trigger.trigger_id, trigger);
if (trigger.evaluation_frequency === 'real_time') {
this.setupRealTimeTrigger(trigger);
}
}
console.log(`Loaded ${triggers.length} active triggers`);
}
subscribeToEvents() {
// Subscribe to real-time events from event bus
this.eventBus.on('payment.bounced', (event) => this.handleEvent('payment_bounced', event));
this.eventBus.on('email.bounced', (event) => this.handleEvent('email_bounced', event));
this.eventBus.on('user.login_failed', (event) => this.handleEvent('login_failed', event));
this.eventBus.on('volunteer.signed_up', (event) => this.handleEvent('volunteer_signup', event));
this.eventBus.on('referral.made', (event) => this.handleEvent('referral_made', event));
this.eventBus.on('payment.received_early', (event) => this.handleEvent('payment_early', event));
// Subscribe to metric updates (from Pattern 6, 7, 10)
this.eventBus.on('metrics.updated', (event) => this.handleMetricsUpdate(event));
}
setupRealTimeTrigger(trigger) {
// Real-time triggers are handled via event subscriptions
console.log(`Real-time trigger active: ${trigger.trigger_name}`);
}
async handleEvent(eventType, eventData) {
// Find triggers that match this event type
const matchingTriggers = Array.from(this.activeTriggers.values())
.filter(t => {
const condition = JSON.parse(t.condition_definition);
return condition.event_type === eventType;
});
for (const trigger of matchingTriggers) {
await this.evaluateTrigger(trigger, eventData);
}
}
async handleMetricsUpdate(event) {
// Check threshold and pattern triggers when metrics update
const { family_id, metric_type, new_value, old_value } = event;
// Threshold triggers
await this.checkThresholdTriggers(family_id, metric_type, new_value);
// Pattern triggers (e.g., drops 30 points in 7 days)
await this.checkPatternTriggers(family_id, metric_type, new_value, old_value);
}
async evaluateTrigger(trigger, context) {
try {
// Parse condition
const condition = JSON.parse(trigger.condition_definition);
// Check if condition met
const conditionMet = await this.evaluateCondition(condition, context);
// Log evaluation
const evalResult = await this.db.query(`
INSERT INTO trigger_evaluations (
trigger_id,
condition_met,
entity_id,
trigger_context
)
OUTPUT INSERTED.evaluation_id
VALUES (?, ?, ?, ?)
`, [
trigger.trigger_id,
conditionMet ? 1 : 0,
context.family_id,
JSON.stringify(context)
]);
const evaluationId = evalResult[0].evaluation_id;
if (conditionMet) {
// Check deduplication
const shouldExecute = await this.checkDeduplication(
trigger,
context.family_id
);
if (shouldExecute) {
await this.executeIntervention(trigger, context, evaluationId);
} else {
await this.db.query(`
UPDATE trigger_evaluations
SET intervention_skipped_reason = 'deduplicated'
WHERE evaluation_id = ?
`, [evaluationId]);
}
}
} catch (error) {
console.error(`Error evaluating trigger ${trigger.trigger_name}:`, error);
}
}
async evaluateCondition(condition, context) {
switch (condition.type) {
case 'simple_match':
return condition.event_type === context.event_type;
case 'threshold':
return this.evaluateThreshold(condition, context);
case 'pattern':
return await this.evaluatePattern(condition, context);
case 'combination':
return await this.evaluateCombination(condition, context);
case 'predictive':
return await this.evaluatePredictive(condition, context);
default:
return false;
}
}
evaluateThreshold(condition, context) {
const value = context[condition.field];
switch (condition.operator) {
case '<': return value < condition.threshold;
case '>': return value > condition.threshold;
case '<=': return value <= condition.threshold;
case '>=': return value >= condition.threshold;
case '==': return value == condition.threshold;
default: return false;
}
}
async evaluatePattern(condition, context) {
// Example: engagement dropped 30 points in 7 days
if (condition.pattern === 'metric_drop') {
const history = await this.db.query(`
SELECT metric_value, calculation_date
FROM family_engagement_metrics_history
WHERE family_id = ?
AND calculation_date >= DATEADD(DAY, -?, GETDATE())
ORDER BY calculation_date DESC
`, [context.family_id, condition.days]);
if (history.length >= 2) {
const drop = history[0].metric_value - history[history.length - 1].metric_value;
return drop >= condition.drop_amount;
}
}
return false;
}
async evaluateCombination(condition, context) {
// Evaluate all sub-conditions
const results = await Promise.all(
condition.conditions.map(c => this.evaluateCondition(c, context))
);
// Combine based on operator
if (condition.operator === 'AND') {
return results.every(r => r);
} else if (condition.operator === 'OR') {
return results.some(r => r);
}
return false;
}
async evaluatePredictive(condition, context) {
// Check ML prediction (from Pattern 12, 13)
const prediction = await this.db.query(`
SELECT
predicted_probability,
confidence_score
FROM ml_predictions
WHERE family_id = ?
AND prediction_type = ?
ORDER BY prediction_date DESC
LIMIT 1
`, [context.family_id, condition.prediction_type]);
if (prediction.length > 0) {
const pred = prediction[0];
return (
pred.predicted_probability > condition.probability_threshold &&
pred.confidence_score > condition.confidence_threshold
);
}
return false;
}
async checkDeduplication(trigger, familyId) {
// Create deduplication key
const dedupeKey = `${trigger.trigger_id}_${familyId}`;
const windowHours = trigger.dedupe_window_hours || 24;
// Check if same intervention sent recently
const recent = await this.db.query(`
SELECT intervention_id
FROM triggered_interventions
WHERE dedupe_key = ?
AND triggered_at >= DATEADD(HOUR, -?, GETDATE())
`, [dedupeKey, windowHours]);
return recent.length === 0;
}
async executeIntervention(trigger, context, evaluationId) {
console.log(`Executing intervention for trigger: ${trigger.trigger_name}`);
// Create intervention record
const dedupeKey = `${trigger.trigger_id}_${context.family_id}`;
const result = await this.db.query(`
INSERT INTO triggered_interventions (
trigger_id,
evaluation_id,
family_id,
action_type,
execution_status,
dedupe_key
)
OUTPUT INSERTED.intervention_id
VALUES (?, ?, ?, ?, 'pending', ?)
`, [
trigger.trigger_id,
evaluationId,
context.family_id,
trigger.intervention_action_type,
dedupeKey
]);
const interventionId = result[0].intervention_id;
// Execute based on priority
const priority = trigger.priority || 3;
if (priority === 1) {
// Critical: Execute immediately
await this.performIntervention(interventionId, trigger, context);
} else {
// Queue for near-term execution
setTimeout(() => {
this.performIntervention(interventionId, trigger, context);
}, this.getPriorityDelay(priority));
}
}
getPriorityDelay(priority) {
// Convert priority to delay in milliseconds
const delays = {
1: 0, // Immediate
2: 60000, // 1 minute
3: 300000, // 5 minutes
4: 900000 // 15 minutes
};
return delays[priority] || delays[3];
}
async performIntervention(interventionId, trigger, context) {
try {
await this.db.query(`
UPDATE triggered_interventions
SET execution_status = 'executing', executed_at = GETDATE()
WHERE intervention_id = ?
`, [interventionId]);
let result;
switch (trigger.intervention_action_type) {
case 'email':
result = await this.sendEmail(trigger, context);
break;
case 'sms':
result = await this.sendSMS(trigger, context);
break;
case 'task':
result = await this.createTask(trigger, context);
break;
case 'workflow':
result = await this.startWorkflow(trigger, context);
break;
case 'alert':
result = await this.sendAlert(trigger, context);
break;
default:
throw new Error(`Unknown action type: ${trigger.intervention_action_type}`);
}
await this.db.query(`
UPDATE triggered_interventions
SET
execution_status = 'completed',
execution_result = ?
WHERE intervention_id = ?
`, [JSON.stringify(result), interventionId]);
// Update effectiveness tracking
await this.updateTriggerEffectiveness(trigger.trigger_id, true);
} catch (error) {
console.error(`Intervention ${interventionId} failed:`, error);
await this.db.query(`
UPDATE triggered_interventions
SET
execution_status = 'failed',
execution_result = ?
WHERE intervention_id = ?
`, [JSON.stringify({ error: error.message }), interventionId]);
await this.updateTriggerEffectiveness(trigger.trigger_id, false);
}
}
async sendEmail(trigger, context) {
const emailService = require('./email-service');
const template = await this.getTemplate(trigger.intervention_template_id);
const family = await this.getFamilyData(context.family_id);
return await emailService.send({
to: family.email,
subject: this.renderTemplate(template.subject, family, context),
html: this.renderTemplate(template.body, family, context)
});
}
async sendSMS(trigger, context) {
const smsService = require('./sms-service');
const template = await this.getTemplate(trigger.intervention_template_id);
const family = await this.getFamilyData(context.family_id);
return await smsService.send({
to: family.phone,
message: this.renderTemplate(template.sms_text, family, context)
});
}
async createTask(trigger, context) {
const family = await this.getFamilyData(context.family_id);
await this.db.query(`
INSERT INTO coordinator_tasks (
family_id,
task_type,
description,
priority,
due_date
) VALUES (?, 'triggered_intervention', ?, ?, DATEADD(DAY, 1, GETDATE()))
`, [
context.family_id,
`${trigger.trigger_name}: ${family.family_name}`,
trigger.priority === 1 ? 'critical' : 'high'
]);
return { task_created: true };
}
async startWorkflow(trigger, context) {
// Start workflow from Pattern 21
const WorkflowTrigger = require('./workflow-trigger');
const workflowTrigger = new WorkflowTrigger(this.db);
const workflowId = await workflowTrigger.createWorkflowInstance(
context.family_id,
trigger.intervention_workflow_id,
`trigger:${trigger.trigger_id}`
);
return { workflow_id: workflowId };
}
async sendAlert(trigger, context) {
// Send alert to coordinator dashboard/mobile app
await this.db.query(`
INSERT INTO coordinator_alerts (
alert_type,
family_id,
message,
priority,
acknowledged
) VALUES ('trigger', ?, ?, ?, 0)
`, [
context.family_id,
`${trigger.trigger_name}`,
trigger.priority
]);
return { alert_sent: true };
}
async checkThresholdTriggers(familyId, metricType, newValue) {
const triggers = Array.from(this.activeTriggers.values())
.filter(t => {
const condition = JSON.parse(t.condition_definition);
return condition.type === 'threshold' && condition.field === metricType;
});
for (const trigger of triggers) {
await this.evaluateTrigger(trigger, {
family_id: familyId,
[metricType]: newValue
});
}
}
async checkPatternTriggers(familyId, metricType, newValue, oldValue) {
const triggers = Array.from(this.activeTriggers.values())
.filter(t => {
const condition = JSON.parse(t.condition_definition);
return condition.type === 'pattern';
});
for (const trigger of triggers) {
await this.evaluateTrigger(trigger, {
family_id: familyId,
metric_type: metricType,
new_value: newValue,
old_value: oldValue
});
}
}
async updateTriggerEffectiveness(triggerId, success) {
await this.db.query(`
MERGE INTO trigger_effectiveness AS target
USING (SELECT ? AS trigger_id) AS source
ON (target.trigger_id = source.trigger_id AND target.calculation_date = CAST(GETDATE() AS DATE))
WHEN MATCHED THEN
UPDATE SET
times_executed = times_executed + 1,
times_successful = times_successful + (CASE WHEN ? = 1 THEN 1 ELSE 0 END),
success_rate = (CAST(times_successful AS FLOAT) / times_executed) * 100
WHEN NOT MATCHED THEN
INSERT (trigger_id, times_executed, times_successful, success_rate, calculation_date)
VALUES (?, 1, ?, ?, CAST(GETDATE() AS DATE));
`, [triggerId, success ? 1 : 0, triggerId, success ? 1 : 0, success ? 100 : 0]);
}
async getTemplate(templateId) {
const template = await this.db.query(`
SELECT * FROM message_templates WHERE template_id = ?
`, [templateId]);
return template[0];
}
async getFamilyData(familyId) {
const family = await this.db.query(`
SELECT * FROM families WHERE family_id = ?
`, [familyId]);
return family[0];
}
renderTemplate(template, family, context) {
return template
.replace(/\{\{family_name\}\}/g, family.family_name)
.replace(/\{\{event_type\}\}/g, context.event_type || '');
}
startPeriodicEvaluation() {
// For triggers that need periodic evaluation (every 5 minutes, hourly, etc.)
setInterval(() => {
this.evaluatePeriodicTriggers();
}, 60000); // Check every minute
}
async evaluatePeriodicTriggers() {
// Implementation for periodic triggers
// (Threshold checks, pattern detection that runs on schedule)
}
}
module.exports = TriggerEngine;
Usage Example
// Define critical triggers
// 1. Payment Bounced (Critical - P1)
await db.query(`
INSERT INTO intervention_triggers (
trigger_name,
description,
trigger_type,
condition_definition,
evaluation_frequency,
priority,
intervention_action_type,
intervention_template_id,
dedupe_window_hours
) VALUES (
'payment_bounced',
'Immediate response when payment bounces',
'single_event',
?,
'real_time',
1, -- Critical
'email',
201,
24
)
`, [JSON.stringify({
type: 'simple_match',
event_type: 'payment_bounced'
})]);
// 2. Email Bounced 3 Times (High - P2)
await db.query(`
INSERT INTO intervention_triggers (
trigger_name,
description,
trigger_type,
condition_definition,
evaluation_frequency,
priority,
intervention_action_type
) VALUES (
'email_bounced_3x',
'Communication breakdown - multiple bounces',
'threshold',
?,
'real_time',
2,
'task'
)
`, [JSON.stringify({
type: 'threshold',
field: 'bounced_email_count',
operator: '>=',
threshold: 3
})]);
// 3. Engagement Cliff (High - P2)
await db.query(`
INSERT INTO intervention_triggers (
trigger_name,
description,
trigger_type,
condition_definition,
evaluation_frequency,
priority,
intervention_action_type,
intervention_workflow_id
) VALUES (
'engagement_cliff',
'Engagement dropped 30+ points in 7 days',
'pattern',
?,
'every_5_minutes',
2,
'workflow',
5 -- Engagement recovery workflow
)
`, [JSON.stringify({
type: 'pattern',
pattern: 'metric_drop',
field: 'engagement_score',
drop_amount: 30,
days: 7
})]);
// 4. First Volunteer (Low - P4 - Positive!)
await db.query(`
INSERT INTO intervention_triggers (
trigger_name,
description,
trigger_type,
condition_definition,
evaluation_frequency,
priority,
intervention_action_type,
intervention_template_id
) VALUES (
'first_volunteer',
'Thank you for first-time volunteer',
'single_event',
?,
'real_time',
4,
'email',
301 -- Thank you template
)
`, [JSON.stringify({
type: 'simple_match',
event_type: 'volunteer_signup'
})]);
// Start engine
const EventEmitter = require('events');
const eventBus = new EventEmitter();
const engine = new TriggerEngine(db, eventBus);
await engine.start();
// System emits events as they happen
eventBus.emit('payment.bounced', {
family_id: 123,
event_type: 'payment_bounced',
amount: 450,
reason: 'insufficient_funds'
});
// → Email sent within seconds!
eventBus.emit('volunteer.signed_up', {
family_id: 456,
event_type: 'volunteer_signup',
event_name: 'Fall Festival',
hours: 4
});
// → Thank you email sent within 5 minutes!
Variations
By Trigger Complexity
Simple Event Triggers: - Payment bounced, login failed, email bounced - Fast, easy to understand - 1:1 event to trigger
Pattern Triggers: - 3 consecutive late payments - Engagement drop over time - Requires historical analysis
Predictive Triggers: - ML model predicts high risk - Combines multiple signals - Most sophisticated
By Response Time
Immediate (Seconds): - Critical events (safety, errors) - Real-time processing required - Highest infrastructure cost
Near-Real-Time (Minutes): - Important but not critical - Batch micro-windows (1-5 min) - Good balance
Delayed (Hours/Days): - Lower priority - Can batch process - Lowest cost
By Action Type
Notify: - Send message (email, SMS, push) - Alert coordinator - Simple, fast
Create Task: - Human follow-up needed - Coordinator intervention - Bridges auto + human
Start Workflow: - Multi-step intervention - Uses Pattern 21 - Complex but powerful
Consequences
Benefits
1. Real-time response React in seconds/minutes, not hours/days.
2. Catch problems early Payment bounced → respond before family gets angry (1 hour vs 24 hours).
3. Never miss critical events Email bounce, engagement cliff, safety concern - all detected instantly.
4. Positive reinforcement First volunteer → immediate thank you (capitalize on excitement).
5. Prevent disasters Engagement cliff caught at 7 days (not 30 days later).
6. Scale infinitely Monitor 10,000 families with same infrastructure as 10.
7. Measurable effectiveness Track which triggers work (optimize over time).
Costs
1. Infrastructure complexity Event-driven architecture more complex than batch.
2. False positive risk Over-sensitive triggers → alert fatigue.
3. Immediate errors If trigger fires incorrectly, damage is instant.
4. Monitoring required Need dashboards to track trigger health.
5. Tuning burden Thresholds need ongoing adjustment.
6. Context loss Automatic systems may miss nuance.
Sample Code
Monitor trigger health:
async function getTriggerHealthDashboard() {
const health = await db.query(`
SELECT
it.trigger_name,
it.priority,
COUNT(ti.intervention_id) as times_fired,
AVG(DATEDIFF(SECOND, ti.triggered_at, ti.executed_at)) as avg_execution_seconds,
SUM(CASE WHEN ti.execution_status = 'failed' THEN 1 ELSE 0 END) as failures
FROM intervention_triggers it
LEFT JOIN triggered_interventions ti ON it.trigger_id = ti.trigger_id
AND ti.triggered_at >= DATEADD(DAY, -7, GETDATE())
WHERE it.active = 1
GROUP BY it.trigger_name, it.priority
ORDER BY it.priority ASC, times_fired DESC
`);
return health;
}
Known Uses
Homeschool Co-op Intelligence Platform - Payment bounce: 5-minute response (vs 24-hour before) - Email bounce 3x: Immediate coordinator alert - Engagement cliff: 15-minute detection, intervention within hour - First volunteer: Thank you within 5 minutes (95% positive feedback)
E-Commerce - Cart abandonment (15-minute trigger) - Payment failure (immediate retry + notification) - Fraud detection (instant blocking)
SaaS - User onboarding triggers (signup → welcome sequence) - Feature usage triggers (first use → tooltip) - Churn signals (activity drop → intervention)
Healthcare - Vital sign alerts (immediate for critical) - Medication adherence (missed dose → reminder) - Appointment no-show (immediate reschedule offer)
Financial Services - Fraud triggers (instant block) - Low balance alerts (real-time notification) - Payment due reminders (configurable timing)
Related Patterns
Requires: - Pattern 1: Universal Event Log - events to trigger on - Pattern 21: Automated Workflow Execution - execute complex interventions
Triggered By: - Pattern 9: Early Warning Signals - detection creates trigger - Pattern 12: Risk Stratification - predictions trigger interventions - Pattern 17: Anomaly Detection - anomalies trigger alerts
Enables: - Pattern 22: Progressive Escalation - first step in sequence - Pattern 26: Feedback Loop - track what triggers work
Enhanced By: - Pattern 24: Template-Based Communication - triggered messages - Pattern 25: Multi-Channel Orchestration - coordinate response
References
Academic Foundations
- Fowler, Martin (2005). "Event Sourcing." https://martinfowler.com/eaaDev/EventSourcing.html - Event-driven architecture patterns
- Richardson, Chris (2018). Microservices Patterns. Manning. ISBN: 978-1617294549 - Event-driven architecture in microservices
- Stopford, Ben (2018). Designing Event-Driven Systems. O'Reilly. ISBN: 978-1492038252 - Kafka and event streams
- Hohpe, Gregor, and Bobby Woolf (2003). Enterprise Integration Patterns. Addison-Wesley. ISBN: 978-0321200686 - https://www.enterpriseintegrationpatterns.com/
Event-Driven Architecture
- Event-Driven Microservices: https://www.confluent.io/blog/event-driven-microservices-patterns/ - Kafka patterns
- The Architect Elevator: Hohpe, G. https://architectelevator.com/ - Cloud and integration patterns
- Domain Events: Vernon, V. (2013). Implementing Domain-Driven Design. Addison-Wesley. ISBN: 978-0321834577
Practical Implementation
- AWS EventBridge: https://aws.amazon.com/eventbridge/ - Serverless event bus with rules engine
- Azure Event Grid: https://azure.microsoft.com/en-us/services/event-grid/ - Event routing for Azure services
- Google Cloud Pub/Sub: https://cloud.google.com/pubsub - Messaging and event ingestion
- Apache Kafka: https://kafka.apache.org/ - Distributed event streaming platform
- RabbitMQ: https://www.rabbitmq.com/ - Message broker with event patterns
Trigger Patterns
- CloudEvents: https://cloudevents.io/ - CNCF standard for event data
- AsyncAPI: https://www.asyncapi.com/ - Event-driven API specification
- Webhook Standards: https://github.com/standard-webhooks/standard-webhooks - Standardized webhooks
Related Trilogy Patterns
- Pattern 9: Early Warning Signals - Warnings trigger interventions
- Pattern 15: Intervention Recommendation Engine - Recommend triggered actions
- Pattern 21: Automated Workflow Execution - Execute triggered workflows
- Pattern 22: Progressive Escalation Sequences - Triggers escalate interventions
- Pattern 24: Webhooks & Event Streaming - Event delivery mechanisms
- Volume 3, Pattern 24: Webhooks & Event Streaming - Form-triggered events
Tools & Services
- Temporal: https://temporal.io/ - Durable workflow execution
- Inngest: https://www.inngest.com/ - Event-driven queue and workflow engine
- Zapier: https://zapier.com/ - No-code event-triggered automation
- n8n: https://n8n.io/ - Open source workflow automation