Pattern 20: Scheduled Actions
Part II: Interaction Patterns - Temporal Patterns
Opening Scenario: The Renewal That Almost Expired
Maria managed software licenses for a 500-person company. Their annual Microsoft 365 license was up for renewal on January 15th.
On January 14th at 4:00 PM, she got an email from the vendor:
Subject: URGENT: License expires tomorrow
Your Microsoft 365 license for 500 users expires
tomorrow, January 15th. If not renewed by midnight,
all users will lose access.
Renewal cost: $87,500
Maria panicked. The finance department had already left for the day. She needed three approvals: her manager, the CFO, and the CEO. Each approval typically took 24 hours.
She had 8 hours to get 72 hours of approvals.
She called her manager at 5 PM. "Why didn't we handle this earlier?"
Maria checked the system:
License Renewal Form
License: Microsoft 365 (500 users)
Cost: $87,500
Renewal Date: 01/15/2025
[Submit for Approval]
// No reminder system
// No advance warning
// No automated scheduling
The system didn't remind anyone. It didn't schedule the renewal process in advance. It just waited for someone to remember.
Maria worked until midnight getting emergency approvals. The license renewed at 11:47 PM. Crisis averted, but barely.
The IT director, Thomas, investigated. He found the system had no scheduling capabilities:
// Bad: No scheduling or reminders
class LicenseManagement {
createRenewal(license, renewalDate) {
database.insert({
license: license,
renewalDate: renewalDate
});
// That's it. No follow-up.
// No reminders.
// No automation.
}
}
// Result: Everything depends on humans remembering
Thomas found more scheduling failures:
Employee review deadlines missed: - 30% of annual reviews completed late - No reminders to managers - No escalation when overdue
Contract expirations discovered too late: - Vendor contracts auto-renewed at unfavorable rates - No 30-day advance warning - No time to renegotiate
Recurring reports forgotten: - Monthly compliance reports required by board - No automatic scheduling - Ad-hoc panic each month
Time-sensitive approvals delayed: - Purchase orders sitting in queue - Approvers didn't know they were urgent - Projects delayed waiting for approvals
Thomas built a comprehensive scheduling system:
class ScheduledActionsSystem {
scheduleAction(action, config) {
const scheduledAction = {
id: this.generateId(),
actionType: action.type,
targetDocument: action.documentId,
// Scheduling
executeAt: config.executeAt,
recurrence: config.recurrence || null,
// Reminders
reminders: config.reminders || [],
escalation: config.escalation || null,
// Action details
action: action.action,
parameters: action.parameters,
// Status
status: 'scheduled',
lastRun: null,
nextRun: config.executeAt,
// Metadata
createdBy: config.userId,
createdAt: new Date()
};
this.scheduler.add(scheduledAction);
return scheduledAction;
}
}
Now when Maria created a license renewal:
License Renewal - Microsoft 365
Renewal Date: January 15, 2025
[✓] Schedule Reminders:
☑ 60 days before (Nov 16) - Notify procurement
☑ 30 days before (Dec 16) - Start approval process
☑ 14 days before (Jan 1) - Escalate if not approved
☑ 7 days before (Jan 8) - Emergency escalation
[✓] Auto-submit approval workflow on Dec 16
[✓] Escalate to CFO if not approved by Jan 1
[✓] Send daily reminders starting Jan 8
[Save Schedule]
On November 16th (60 days before renewal):
📧 Reminder: Microsoft 365 License Renewal
License renews on January 15, 2025 (60 days)
Cost: $87,500
Action needed: Begin budget planning
Next reminder: December 16 (30 days before)
On December 16th (30 days before renewal):
🔔 AUTO-SUBMITTED: License Renewal Approval
The system has automatically initiated the approval
workflow for Microsoft 365 license renewal.
Current status: Pending Manager Approval
Assigned to: Karen Wilson
Deadline: January 15, 2025 (30 days)
On January 1st (14 days before, not yet approved):
⚠️ ESCALATION: License Renewal Urgent
Microsoft 365 license renewal still pending approval.
Renewal date: January 15 (14 days away)
Escalating to: CFO (Sarah Chen)
Daily reminders will begin on January 8 if not resolved.
Zero last-minute emergencies. Everything scheduled. Everyone reminded. Complete planning.
Context
Scheduled Actions applies when:
Future planning required: Actions need to happen at specific future times
Recurring tasks exist: Monthly reports, annual reviews, quarterly audits
Deadlines are critical: Missing deadline has serious consequences
Reminders needed: People forget, systems shouldn't
Multi-step processes: Approvals, reviews, escalations over time
Automation desired: Reduce manual intervention
Coordination needed: Multiple people, multiple timelines
Compliance mandates: Regular required actions (audits, certifications)
Problem Statement
Most systems lack scheduling capabilities, forcing users to remember everything:
No future actions:
// Bad: Can only act on present
function submitForm(data) {
processImmediately(data);
}
// What if user wants to:
// - Submit next Monday
// - Schedule for end of quarter
// - Auto-submit on deadline
// Not possible!
No reminders:
// Bad: Set deadline, hope someone remembers
database.update(task, {
deadline: '2025-03-15'
});
// No notification system
// No advance warning
// User must remember to check
No recurrence:
// Bad: Manual creation each time
function createMonthlyReport() {
// User must:
// 1. Remember it's time
// 2. Create report manually
// 3. Submit manually
// Every single month
}
// Should be: Schedule once, runs automatically
No escalation:
// Bad: Task sits forever if ignored
{
assignedTo: 'manager@company.com',
dueDate: '2024-12-15'
}
// Manager ignores it
// No escalation to their manager
// No alternative assignee
// Task dies
No conditional scheduling:
// Bad: Fixed schedule only
schedule.every('monday').run(task);
// What if:
// - Skip holidays
// - Only on business days
// - Only if previous task completed
// Not supported!
No time zone handling:
// Bad: Assumes one time zone
schedule.at('9:00 AM').run(task);
// 9 AM in which time zone?
// User in Tokyo gets 2 AM notification
// Terrible UX
We need comprehensive scheduling that handles future actions, reminders, recurrence, escalation, and intelligent timing.
Forces
Automation vs Control
- Automation reduces manual work
- But users want control
- Balance convenience with oversight
Precision vs Flexibility
- Exact scheduling is predictable
- But needs flexibility for exceptions
- Balance reliability with adaptability
Reminders vs Noise
- Reminders prevent forgetting
- But too many annoy users
- Balance helpfulness with intrusiveness
Simplicity vs Power
- Simple scheduling is easy
- But complex needs require features
- Balance usability with capability
Local vs Global
- User's local time is intuitive
- But system time is consistent
- Balance user experience with reliability
Solution
Implement comprehensive scheduling system that supports future-dated actions, intelligent reminders with escalation, recurring tasks with flexible patterns, time zone awareness, and conditional execution based on business rules.
The pattern has seven key strategies:
1. Future Action Scheduling
Schedule actions to execute at specific future times:
class FutureActionScheduler {
constructor() {
this.scheduledActions = new Map();
this.executor = new ActionExecutor();
}
scheduleAction(action, executeAt, options = {}) {
const scheduled = {
id: this.generateId(),
action: action,
executeAt: new Date(executeAt),
// Options
timezone: options.timezone || 'UTC',
condition: options.condition || null,
onSuccess: options.onSuccess || null,
onFailure: options.onFailure || null,
maxRetries: options.maxRetries || 3,
retryDelay: options.retryDelay || 60000, // 1 minute
// Status
status: 'scheduled',
attempts: 0,
lastAttempt: null,
error: null
};
this.scheduledActions.set(scheduled.id, scheduled);
this.scheduleExecution(scheduled);
return scheduled;
}
scheduleExecution(scheduled) {
const now = new Date();
const delay = scheduled.executeAt - now;
if (delay <= 0) {
// Execute immediately
this.executeAction(scheduled);
} else {
// Schedule for future
setTimeout(() => {
this.executeAction(scheduled);
}, delay);
}
}
async executeAction(scheduled) {
// Check condition
if (scheduled.condition) {
const conditionMet = await this.evaluateCondition(scheduled.condition);
if (!conditionMet) {
scheduled.status = 'condition-not-met';
return;
}
}
// Execute
scheduled.status = 'executing';
scheduled.lastAttempt = new Date();
scheduled.attempts++;
try {
const result = await this.executor.execute(scheduled.action);
scheduled.status = 'completed';
scheduled.result = result;
// Success callback
if (scheduled.onSuccess) {
await scheduled.onSuccess(result);
}
} catch (error) {
scheduled.error = error.message;
// Retry?
if (scheduled.attempts < scheduled.maxRetries) {
scheduled.status = 'retrying';
setTimeout(() => {
this.executeAction(scheduled);
}, scheduled.retryDelay);
} else {
scheduled.status = 'failed';
// Failure callback
if (scheduled.onFailure) {
await scheduled.onFailure(error);
}
}
}
}
cancelScheduledAction(actionId) {
const scheduled = this.scheduledActions.get(actionId);
if (scheduled && scheduled.status === 'scheduled') {
scheduled.status = 'cancelled';
return true;
}
return false;
}
rescheduleAction(actionId, newExecuteAt) {
const scheduled = this.scheduledActions.get(actionId);
if (scheduled) {
scheduled.executeAt = new Date(newExecuteAt);
scheduled.status = 'scheduled';
this.scheduleExecution(scheduled);
return true;
}
return false;
}
}
2. Intelligent Reminder System
Send timely reminders with escalation:
class ReminderSystem {
constructor(scheduler) {
this.scheduler = scheduler;
this.reminders = new Map();
}
createReminderSequence(target, deadline, config) {
const sequence = {
id: this.generateId(),
target: target, // Document, task, etc.
deadline: new Date(deadline),
reminders: [],
escalation: config.escalation || null,
status: 'active',
sentReminders: []
};
// Schedule reminders at intervals before deadline
config.intervals.forEach(interval => {
const reminderTime = new Date(deadline);
reminderTime.setTime(reminderTime.getTime() - interval);
const reminder = {
id: this.generateId(),
sequenceId: sequence.id,
time: reminderTime,
interval: interval,
recipients: config.recipients,
template: this.getTemplate(interval, deadline),
severity: this.getSeverity(interval, deadline),
sent: false
};
sequence.reminders.push(reminder);
// Schedule the reminder
this.scheduler.scheduleAction({
type: 'send-reminder',
reminder: reminder
}, reminderTime);
});
// Schedule escalation if provided
if (config.escalation) {
this.scheduleEscalation(sequence, deadline, config.escalation);
}
this.reminders.set(sequence.id, sequence);
return sequence;
}
getSeverity(intervalBeforeDeadline, deadline) {
const days = intervalBeforeDeadline / (1000 * 60 * 60 * 24);
if (days <= 1) return 'urgent';
if (days <= 7) return 'high';
if (days <= 14) return 'medium';
return 'low';
}
getTemplate(interval, deadline) {
const days = Math.floor(interval / (1000 * 60 * 60 * 24));
if (days === 0) {
return {
subject: '🚨 URGENT: Deadline is TODAY',
urgency: 'critical'
};
} else if (days === 1) {
return {
subject: '⚠️ Reminder: Deadline Tomorrow',
urgency: 'high'
};
} else if (days <= 7) {
return {
subject: `📅 Reminder: Deadline in ${days} days`,
urgency: 'medium'
};
} else {
return {
subject: `📆 Upcoming: Deadline in ${days} days`,
urgency: 'low'
};
}
}
scheduleEscalation(sequence, deadline, escalationConfig) {
const checkTime = new Date(deadline);
checkTime.setTime(checkTime.getTime() + escalationConfig.afterDeadline);
this.scheduler.scheduleAction({
type: 'check-escalation',
sequence: sequence.id,
escalateTo: escalationConfig.escalateTo,
condition: {
type: 'not-completed',
target: sequence.target
}
}, checkTime);
}
// Example: License renewal reminders
scheduleLicenseReminders(license, renewalDate) {
return this.createReminderSequence(
license,
renewalDate,
{
intervals: [
60 * 24 * 60 * 60 * 1000, // 60 days
30 * 24 * 60 * 60 * 1000, // 30 days
14 * 24 * 60 * 60 * 1000, // 14 days
7 * 24 * 60 * 60 * 1000, // 7 days
1 * 24 * 60 * 60 * 1000 // 1 day
],
recipients: ['procurement@company.com', 'maria@company.com'],
escalation: {
afterDeadline: 24 * 60 * 60 * 1000, // 1 day after
escalateTo: ['cfo@company.com']
}
}
);
}
}
3. Recurring Task Scheduler
Handle recurring events with complex patterns:
class RecurringScheduler {
constructor() {
this.recurrences = new Map();
}
scheduleRecurring(action, pattern, options = {}) {
const recurrence = {
id: this.generateId(),
action: action,
pattern: pattern,
// Options
startDate: options.startDate || new Date(),
endDate: options.endDate || null,
timezone: options.timezone || 'UTC',
skipConditions: options.skipConditions || [],
// Status
status: 'active',
executions: [],
nextExecution: null,
lastExecution: null
};
// Calculate next execution
recurrence.nextExecution = this.calculateNextExecution(recurrence);
this.recurrences.set(recurrence.id, recurrence);
this.scheduleNext(recurrence);
return recurrence;
}
calculateNextExecution(recurrence) {
const pattern = recurrence.pattern;
let next = new Date(recurrence.lastExecution || recurrence.startDate);
switch(pattern.type) {
case 'daily':
next.setDate(next.getDate() + pattern.interval);
break;
case 'weekly':
next.setDate(next.getDate() + (7 * pattern.interval));
break;
case 'monthly':
next.setMonth(next.getMonth() + pattern.interval);
break;
case 'yearly':
next.setFullYear(next.getFullYear() + pattern.interval);
break;
case 'custom':
next = pattern.calculateNext(next);
break;
}
// Apply skip conditions
while (this.shouldSkip(next, recurrence.skipConditions)) {
next = this.calculateNextExecution({
...recurrence,
lastExecution: next
});
}
// Check end date
if (recurrence.endDate && next > recurrence.endDate) {
return null;
}
return next;
}
shouldSkip(date, skipConditions) {
return skipConditions.some(condition => {
switch(condition.type) {
case 'weekend':
const day = date.getDay();
return day === 0 || day === 6;
case 'holiday':
return this.isHoliday(date);
case 'specific-dates':
return condition.dates.some(d =>
this.isSameDay(date, new Date(d))
);
case 'custom':
return condition.check(date);
default:
return false;
}
});
}
scheduleNext(recurrence) {
if (recurrence.nextExecution) {
this.scheduler.scheduleAction({
type: 'execute-recurring',
recurrenceId: recurrence.id,
action: recurrence.action
}, recurrence.nextExecution, {
onSuccess: () => {
this.recordExecution(recurrence, true);
this.scheduleNext(recurrence);
},
onFailure: (error) => {
this.recordExecution(recurrence, false, error);
}
});
} else {
recurrence.status = 'completed';
}
}
recordExecution(recurrence, success, error = null) {
recurrence.executions.push({
timestamp: new Date(),
success: success,
error: error
});
recurrence.lastExecution = new Date();
recurrence.nextExecution = this.calculateNextExecution(recurrence);
}
// Example patterns
// Every weekday at 9 AM
everyWeekday(action, time = '09:00') {
return this.scheduleRecurring(action, {
type: 'daily',
interval: 1
}, {
skipConditions: [
{ type: 'weekend' },
{ type: 'holiday' }
]
});
}
// First Monday of each month
firstMondayOfMonth(action) {
return this.scheduleRecurring(action, {
type: 'custom',
calculateNext: (current) => {
const next = new Date(current);
next.setMonth(next.getMonth() + 1);
next.setDate(1);
// Find first Monday
while (next.getDay() !== 1) {
next.setDate(next.getDate() + 1);
}
return next;
}
});
}
// Last business day of quarter
endOfQuarter(action) {
return this.scheduleRecurring(action, {
type: 'custom',
calculateNext: (current) => {
const next = new Date(current);
const month = next.getMonth();
// Find end of quarter
const quarterEndMonth = Math.floor(month / 3) * 3 + 2;
next.setMonth(quarterEndMonth + 3);
next.setDate(0); // Last day of previous month
// Back up to business day
while (!this.isBusinessDay(next)) {
next.setDate(next.getDate() - 1);
}
return next;
}
});
}
}
4. Conditional Execution
Execute based on conditions, not just time:
class ConditionalScheduler {
constructor() {
this.conditions = new Map();
}
scheduleWhen(action, condition, options = {}) {
const conditional = {
id: this.generateId(),
action: action,
condition: condition,
// Options
timeout: options.timeout || null,
maxChecks: options.maxChecks || 1000,
checkInterval: options.checkInterval || 60000, // 1 minute
// Status
status: 'waiting',
checks: 0,
lastCheck: null
};
this.conditions.set(conditional.id, conditional);
this.startChecking(conditional);
return conditional;
}
async startChecking(conditional) {
const checkCondition = async () => {
conditional.lastCheck = new Date();
conditional.checks++;
try {
const conditionMet = await this.evaluateCondition(conditional.condition);
if (conditionMet) {
// Execute action
conditional.status = 'executing';
await this.executeAction(conditional.action);
conditional.status = 'completed';
return;
}
// Check limits
if (conditional.maxChecks && conditional.checks >= conditional.maxChecks) {
conditional.status = 'max-checks-exceeded';
return;
}
if (conditional.timeout) {
const elapsed = Date.now() - conditional.createdAt;
if (elapsed >= conditional.timeout) {
conditional.status = 'timeout';
return;
}
}
// Schedule next check
setTimeout(checkCondition, conditional.checkInterval);
} catch (error) {
conditional.status = 'error';
conditional.error = error.message;
}
};
checkCondition();
}
async evaluateCondition(condition) {
switch(condition.type) {
case 'document-state':
return await this.checkDocumentState(
condition.documentId,
condition.state
);
case 'field-value':
return await this.checkFieldValue(
condition.documentId,
condition.field,
condition.value
);
case 'approval-complete':
return await this.checkApprovalComplete(condition.documentId);
case 'user-action':
return await this.checkUserAction(
condition.userId,
condition.action
);
case 'time-elapsed':
return this.checkTimeElapsed(
condition.startTime,
condition.duration
);
case 'custom':
return await condition.evaluate();
default:
return false;
}
}
// Example: Submit form when all approvals complete
submitWhenApproved(formId) {
return this.scheduleWhen(
{
type: 'submit-form',
formId: formId
},
{
type: 'approval-complete',
documentId: formId
},
{
timeout: 30 * 24 * 60 * 60 * 1000, // 30 days
checkInterval: 60 * 60 * 1000 // Check every hour
}
);
}
// Example: Escalate if not completed within 24 hours
escalateIfDelayed(taskId, assignee, escalateTo) {
return this.scheduleWhen(
{
type: 'escalate-task',
taskId: taskId,
from: assignee,
to: escalateTo
},
{
type: 'time-elapsed',
startTime: new Date(),
duration: 24 * 60 * 60 * 1000 // 24 hours
}
);
}
}
5. Time Zone Management
Handle scheduling across time zones:
class TimeZoneScheduler {
constructor() {
this.timezones = new Map();
}
scheduleInUserTimezone(action, localTime, userTimezone) {
// Convert user's local time to UTC
const utcTime = this.convertToUTC(localTime, userTimezone);
return this.scheduler.scheduleAction(action, utcTime, {
timezone: userTimezone,
displayTime: localTime
});
}
scheduleForMultipleTimezones(action, localTime, users) {
// Schedule same action for each user in their local time
const scheduled = [];
users.forEach(user => {
const userScheduled = this.scheduleInUserTimezone(
{
...action,
userId: user.id
},
localTime,
user.timezone
);
scheduled.push(userScheduled);
});
return scheduled;
}
convertToUTC(localTime, timezone) {
// Use proper timezone library (e.g., moment-timezone)
const moment = require('moment-timezone');
return moment.tz(localTime, timezone).utc().toDate();
}
// Example: Daily standup at 9 AM in each team member's timezone
scheduleDailyStandup(team) {
return this.scheduleForMultipleTimezones(
{
type: 'send-standup-reminder',
message: 'Time for daily standup!'
},
'09:00',
team.members
);
}
// Example: Global webinar at specific time
scheduleGlobalEvent(event, eventTime, timezone) {
// Event time is in organizer's timezone
// Show local time to each participant
const utcTime = this.convertToUTC(eventTime, timezone);
event.participants.forEach(participant => {
const localTime = this.convertFromUTC(utcTime, participant.timezone);
this.sendReminder(participant, {
event: event,
utcTime: utcTime,
localTime: localTime,
timezone: participant.timezone
});
});
}
}
6. Deadline Management
Track and enforce deadlines:
class DeadlineManager {
constructor(reminderSystem, escalationSystem) {
this.reminderSystem = reminderSystem;
this.escalationSystem = escalationSystem;
this.deadlines = new Map();
}
setDeadline(target, deadline, config) {
const deadlineEntry = {
id: this.generateId(),
target: target,
deadline: new Date(deadline),
// Configuration
reminders: config.reminders || [],
escalation: config.escalation || null,
autoActions: config.autoActions || [],
// Status
status: 'active',
completed: false,
completedAt: null,
missedDeadline: false
};
// Schedule reminders
if (config.reminders) {
this.reminderSystem.createReminderSequence(
target,
deadline,
config.reminders
);
}
// Schedule deadline check
this.scheduler.scheduleAction({
type: 'check-deadline',
deadlineId: deadlineEntry.id
}, deadline);
// Schedule auto-actions
config.autoActions.forEach(autoAction => {
const actionTime = new Date(deadline);
actionTime.setTime(actionTime.getTime() + autoAction.offset);
this.scheduler.scheduleAction(
autoAction.action,
actionTime,
{
condition: {
type: 'deadline-not-met',
deadlineId: deadlineEntry.id
}
}
);
});
this.deadlines.set(deadlineEntry.id, deadlineEntry);
return deadlineEntry;
}
markCompleted(deadlineId) {
const deadline = this.deadlines.get(deadlineId);
if (deadline) {
deadline.completed = true;
deadline.completedAt = new Date();
deadline.status = 'completed';
// Check if missed
if (deadline.completedAt > deadline.deadline) {
deadline.missedDeadline = true;
deadline.daysLate = this.calculateDaysLate(
deadline.deadline,
deadline.completedAt
);
}
// Cancel pending reminders
this.reminderSystem.cancelReminders(deadlineId);
}
}
// Example: Project milestone with escalation
setProjectMilestone(project, milestone, dueDate) {
return this.setDeadline(
{ type: 'milestone', project: project, milestone: milestone },
dueDate,
{
reminders: {
intervals: [
14 * 24 * 60 * 60 * 1000, // 14 days
7 * 24 * 60 * 60 * 1000, // 7 days
3 * 24 * 60 * 60 * 1000, // 3 days
1 * 24 * 60 * 60 * 1000 // 1 day
],
recipients: [project.manager.email]
},
escalation: {
afterDeadline: 24 * 60 * 60 * 1000,
escalateTo: [project.director.email]
},
autoActions: [
{
offset: 0, // On deadline
action: {
type: 'update-status',
status: 'overdue'
}
},
{
offset: 7 * 24 * 60 * 60 * 1000, // 7 days after
action: {
type: 'escalate-to-executive',
executive: project.vp
}
}
]
}
);
}
}
7. Calendar Integration
Visualize and manage scheduled actions:
class ScheduleCalendar {
constructor(scheduler) {
this.scheduler = scheduler;
}
getUpcomingActions(userId, timeRange) {
const upcoming = [];
// Get scheduled actions
const scheduled = this.scheduler.getScheduledForUser(userId);
scheduled.forEach(action => {
if (this.isInRange(action.executeAt, timeRange)) {
upcoming.push({
type: 'scheduled-action',
time: action.executeAt,
action: action.action,
status: action.status
});
}
});
// Get reminders
const reminders = this.reminderSystem.getUpcomingReminders(userId);
reminders.forEach(reminder => {
if (this.isInRange(reminder.time, timeRange)) {
upcoming.push({
type: 'reminder',
time: reminder.time,
severity: reminder.severity,
message: reminder.template.subject
});
}
});
// Get deadlines
const deadlines = this.deadlineManager.getActiveDeadlines(userId);
deadlines.forEach(deadline => {
if (this.isInRange(deadline.deadline, timeRange)) {
upcoming.push({
type: 'deadline',
time: deadline.deadline,
target: deadline.target,
status: deadline.status
});
}
});
// Sort by time
return upcoming.sort((a, b) => a.time - b.time);
}
renderCalendar(userId, month, year) {
const upcoming = this.getUpcomingActions(userId, {
start: new Date(year, month, 1),
end: new Date(year, month + 1, 0)
});
return `
<div class="schedule-calendar">
<div class="calendar-header">
<h3>${this.getMonthName(month)} ${year}</h3>
</div>
<div class="calendar-grid">
${this.renderMonthGrid(upcoming, month, year)}
</div>
<div class="calendar-legend">
<span class="legend-item scheduled">Scheduled Actions</span>
<span class="legend-item reminder">Reminders</span>
<span class="legend-item deadline">Deadlines</span>
</div>
</div>
`;
}
renderDayCell(date, actions) {
const hasActions = actions.length > 0;
return `
<div class="calendar-day ${hasActions ? 'has-actions' : ''}">
<div class="day-number">${date.getDate()}</div>
<div class="day-actions">
${actions.map(a => this.renderDayAction(a)).join('')}
</div>
</div>
`;
}
renderDayAction(action) {
return `
<div class="day-action ${action.type}">
<span class="action-icon">${this.getIcon(action.type)}</span>
<span class="action-time">${this.formatTime(action.time)}</span>
</div>
`;
}
}
Implementation Details
Complete Scheduled Actions System
class ComprehensiveSchedulingSystem {
constructor() {
this.futureScheduler = new FutureActionScheduler();
this.reminderSystem = new ReminderSystem(this.futureScheduler);
this.recurringScheduler = new RecurringScheduler();
this.conditionalScheduler = new ConditionalScheduler();
this.timezoneScheduler = new TimeZoneScheduler();
this.deadlineManager = new DeadlineManager(
this.reminderSystem,
this.escalationSystem
);
this.calendar = new ScheduleCalendar(this);
}
// Schedule one-time future action
schedule(action, executeAt, options) {
return this.futureScheduler.scheduleAction(action, executeAt, options);
}
// Schedule recurring action
scheduleRecurring(action, pattern, options) {
return this.recurringScheduler.scheduleRecurring(action, pattern, options);
}
// Set up reminders
remind(target, deadline, reminderConfig) {
return this.reminderSystem.createReminderSequence(
target,
deadline,
reminderConfig
);
}
// Schedule with condition
scheduleWhen(action, condition, options) {
return this.conditionalScheduler.scheduleWhen(action, condition, options);
}
// Set deadline with reminders and escalation
deadline(target, dueDate, config) {
return this.deadlineManager.setDeadline(target, dueDate, config);
}
// Get user's schedule
getSchedule(userId, timeRange) {
return this.calendar.getUpcomingActions(userId, timeRange);
}
}
// Usage examples
const scheduling = new ComprehensiveSchedulingSystem();
// 1. Schedule license renewal with reminders
scheduling.deadline(
{ type: 'license', id: 'microsoft-365' },
'2025-01-15',
{
reminders: {
intervals: [
60 * 24 * 60 * 60 * 1000, // 60 days
30 * 24 * 60 * 60 * 1000, // 30 days
14 * 24 * 60 * 60 * 1000, // 14 days
7 * 24 * 60 * 60 * 1000 // 7 days
],
recipients: ['procurement@company.com']
},
autoActions: [
{
offset: -30 * 24 * 60 * 60 * 1000, // 30 days before
action: {
type: 'submit-approval-workflow'
}
}
]
}
);
// 2. Schedule recurring monthly report
scheduling.scheduleRecurring(
{
type: 'generate-report',
reportType: 'monthly-compliance'
},
{
type: 'custom',
calculateNext: (current) => {
// Last business day of month
const next = new Date(current);
next.setMonth(next.getMonth() + 1);
next.setDate(0); // Last day of month
while (!isBusinessDay(next)) {
next.setDate(next.getDate() - 1);
}
return next;
}
}
);
// 3. Auto-submit form when approvals complete
scheduling.scheduleWhen(
{
type: 'submit-form',
formId: 'purchase-order-3847'
},
{
type: 'approval-complete',
documentId: 'purchase-order-3847'
}
);
Consequences
Benefits
Never Miss Deadlines: - Automatic reminders - Escalation mechanisms - Clear visibility
Reduced Manual Work: - Recurring tasks automated - No remembering required - System handles timing
Better Planning: - Future actions scheduled - Dependencies tracked - Timeline visualized
Improved Compliance: - Required tasks automated - Audit trail of scheduling - Proof of diligence
Enhanced Coordination: - Multi-person workflows - Time zone handling - Synchronized actions
Liabilities
Complexity: - Scheduling logic intricate - Many configuration options - Learning curve exists
Over-Reliance: - Users depend on system - Notifications ignored - "Alert fatigue"
Maintenance: - Schedules need updating - Edge cases arise - System must be monitored
Time Zone Confusion: - DST changes problematic - Multiple zones complex - Errors possible
False Security: - System can fail - Notifications can be missed - Backup plans still needed
Domain Examples
IT: License Management
scheduling.deadline(
{ type: 'license', name: 'Microsoft 365' },
renewalDate,
{
reminders: { intervals: [60, 30, 14, 7, 1] },
autoActions: [
{ offset: -30, action: 'start-approval' },
{ offset: -7, action: 'emergency-escalation' }
]
}
);
HR: Performance Reviews
scheduling.scheduleRecurring(
{
type: 'performance-review',
employees: allEmployees
},
{ type: 'yearly', month: 3 }, // March
{
reminders: {
intervals: [30, 14, 7, 1],
recipients: ['managers']
}
}
);
Finance: Quarterly Reports
scheduling.scheduleRecurring(
{
type: 'quarterly-report',
reportType: 'financial-summary'
},
{
type: 'custom',
calculateNext: endOfQuarter
}
);
Legal: Contract Renewals
contracts.forEach(contract => {
scheduling.deadline(
contract,
contract.expirationDate,
{
reminders: {
intervals: [90, 60, 30],
recipients: [contract.owner]
},
escalation: {
afterDeadline: 0,
escalateTo: ['legal-team@company.com']
}
}
);
});
Related Patterns
Prerequisites: - Volume 3, Pattern 16: Temporal Validation (valid dates for scheduling) - Volume 3, Pattern 17: State-Aware Behavior (scheduled state transitions)
Synergies: - Volume 3, Pattern 18: Audit Trail (log all scheduled actions) - Volume 3, Pattern 19: Version Control (scheduled versioning) - All patterns (scheduling enhances all workflows)
Conflicts: - Real-time only systems - Ad-hoc workflows - Unpredictable processes
Alternatives: - Manual calendar reminders - Cron jobs (simple scheduling) - External scheduling services
Known Uses
Calendar Apps: Google Calendar, Outlook scheduled events
Task Management: Asana, Trello due dates and reminders
Email: Scheduled send, follow-up reminders
Backup Systems: Scheduled backups with retention
CI/CD: Scheduled builds and deployments
Monitoring: Scheduled health checks and reports
Marketing: Campaign scheduling and drip emails
Maintenance: Scheduled system maintenance windows
Further Reading
Academic Foundations
- Job Scheduling: Pinedo, M.L. (2016). Scheduling: Theory, Algorithms, and Systems (5th ed.). Springer. ISBN: 978-3319265803
- Cron Expression Theory: Vixie, P. (1994). "Cron Man Page." https://man7.org/linux/man-pages/man5/crontab.5.html
- Distributed Scheduling: Chapin, S.J., et al. (1999). "Distributed and parallel job scheduling." IEEE Distributed Systems Online.
Practical Implementation
- node-cron: https://github.com/node-cron/node-cron - Cron-like task scheduler for Node.js
- node-schedule: https://github.com/node-schedule/node-schedule - Flexible job scheduler
- Agenda: https://github.com/agenda/agenda - Lightweight job scheduling for Node.js with MongoDB
- Bull: https://github.com/OptimalBits/bull - Redis-based queue for Node.js
- BullMQ: https://docs.bullmq.io/ - Modern Bull implementation
Standards & Specifications
- Cron Expression Format: https://en.wikipedia.org/wiki/Cron - Unix cron syntax
- ISO 8601 Intervals: https://en.wikipedia.org/wiki/ISO_8601#Time_intervals - Duration and repeat format
- iCalendar RRULE: https://tools.ietf.org/html/rfc5545#section-3.3.10 - Recurrence rules
Related Trilogy Patterns
- Pattern 21: Temporal Validation - Schedule timing validation
- Pattern 22: State-Aware Behavior - Scheduled state transitions
- Pattern 23: Audit Trail - Log scheduled action execution
- Volume 2, Pattern 14: Predictive Time Windows - Predict scheduled outcomes
- Volume 1, Chapter 6: Business Domain Patterns - Scheduling architecture
Tools & Services
- Celery: https://docs.celeryproject.org/ - Distributed task queue (Python)
- Quartz: http://www.quartz-scheduler.org/ - Enterprise job scheduler (Java)
- Hangfire: https://www.hangfire.io/ - Background job processing (.NET)
- APScheduler: https://apscheduler.readthedocs.io/ - Advanced Python Scheduler
- AWS EventBridge: https://aws.amazon.com/eventbridge/ - Serverless event bus with scheduling
- Google Cloud Scheduler: https://cloud.google.com/scheduler - Fully managed cron service
Implementation Examples
- Cron Best Practices: https://healthchecks.io/docs/cron_best_practices/
- Task Scheduling Patterns: https://www.enterpriseintegrationpatterns.com/patterns/messaging/DurableSubscription.html
- Building Reliable Schedulers: https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/