Building Real-Time Task Notification Infrastructure for maintenance.queenofsandiego.com
When Travis added new maintenance tasks via SMS and we discovered no reliable way to surface them to the team, it became clear that the maintenance tool needed a proper notification system. This post documents the infrastructure and architectural decisions made to implement task notifications that scale with criticality and integrate seamlessly with existing booking automation.
The Problem
The maintenance.queenofsandiego.com tool was functioning as a static task tracker, but lacked:
- Real-time notification when new tasks were added
- A way to alert Sergio and other team members appropriately
- Intelligent routing based on task criticality
- Audit trail for when tasks were added and by whom
Additionally, we needed to establish a testing pattern for staging vs. production environments when both share infrastructure.
Architecture Overview
The solution implements a three-tier notification system:
- Persistence Layer (Lambda): Stores task data durably and triggers notifications
- Notification Router (GAS): Routes alerts based on criticality and time of day
- UI Integration (HTML/JavaScript): Surfaces new tasks in the staging tool interface
Implementation Details
1. Persistence Layer: MaintenancePersistence.gs
Created as a new Google Apps Script file at /Users/cb/Documents/repos/sites/queenofsandiego.com/MaintenancePersistence.gs, this module handles all task storage and persistence operations:
// Writes task data to Sheets for audit trail
function persistMaintenanceTask(taskData) {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('Tasks');
const timestamp = new Date();
sheet.appendRow([
timestamp,
taskData.title,
taskData.criticality,
taskData.assignedTo,
taskData.description,
taskData.addedBy
]);
}
// Queries tasks added in last N hours
function getRecentTasks(hoursBack = 24) {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('Tasks');
const data = sheet.getDataRange().getValues();
const cutoffTime = new Date(Date.now() - hoursBack * 3600000);
return data.filter(row => new Date(row[0]) > cutoffTime);
}
This module intentionally uses Google Sheets rather than a Lambda-backed database for several reasons:
- Existing Integration: The booking automation system already uses Sheets extensively, reducing operational complexity
- Audit Trail: Sheets provides built-in version history and row-level timestamps
- Team Visibility: Team members can access historical task data without additional access management
- No Additional Services: Avoids introducing a new data store to maintain
2. Notification Router: MaintenanceNotificationHandler in BookingAutomation.gs
Added to the existing BookingAutomation.gs routing layer, the notification handler integrates with the existing action dispatch system:
// In BookingAutomation.gs doPost handler
if (params.action === 'log_maintenance') {
const taskData = {
title: params.title,
criticality: params.criticality || 'normal',
assignedTo: params.assignedTo || 'Sergio',
description: params.description,
addedBy: params.source || 'mobile'
};
persistMaintenanceTask(taskData);
routeMaintenanceNotification(taskData);
return ContentService.createTextOutput('OK');
}
function routeMaintenanceNotification(taskData) {
const hour = new Date().getHours();
const isBusinessHours = hour >= 8 && hour < 18;
if (taskData.criticality === 'critical') {
// Always notify immediately for critical tasks
sendMaintenanceAlert(
taskData,
'immediate',
['jadasailing@gmail.com']
);
} else if (isBusinessHours) {
// Real-time notification during business hours
sendMaintenanceAlert(
taskData,
'immediate',
['jadasailing@gmail.com']
);
} else {
// Batch for digest during off-hours
queueForDailyDigest(taskData);
}
}
Design Decision: Time-Based Routing
Research from high-performing ops teams (Google SRE, PagerDuty studies) shows that constant notifications during off-hours reduce team effectiveness and increase alert fatigue. Our approach:
- Critical tasks: Immediate notification regardless of time (uses industry-standard definition: tasks blocking operations or safety concerns)
- Normal tasks during business hours: Immediate notification (8 AM - 6 PM local time)
- Normal tasks off-hours: Batch into next morning digest (queued in Sheets with digest flag)
3. Staging/Production Separation Pattern
For the maintenance tool, we implemented environment detection via email routing:
// In MaintenanceNotificationHandler
function sendMaintenanceAlert(taskData, urgency, recipients) {
const testingMode = true; // Toggle this per deployment
const targetEmail = testingMode ?
'jadasailing@gmail.com' :
recipients;
const subject = `[${taskData.criticality.toUpperCase()}] New Maintenance Task`;
const body = formatTaskNotification(taskData);
GmailApp.sendEmail(
targetEmail,
subject,
body,
{ from: 'jada-maintenance@queenofsandiego.com' }
);
}
Why this pattern?
- The maintenance tool is served from the same HTML/CloudFront distribution for both staging and production
- Rather than duplicating infrastructure, we route notifications to test email during development
- Flag-based control allows QA testing without infrastructure duplication
- When ready for production, a single flag toggle and script deployment activates real notification recipients
4. Calendar Integration: MaintenanceCalendar.gs
Created new file at /Users/cb/Documents/repos/sites/queenofsandiego.com/MaintenanceCalendar.gs to synchronize tasks with the "Jada Maintenance" calendar:
function syncTaskToCalendar(taskData) {
const calendar = CalendarApp.getCalendarById(
'jada-maintenance@queenofsandiego.com'
);
const event = calendar.createEvent(
`[${taskData.criticality}] ${taskData.title}`,
new Date(),
new Date(Date.now() + 3600000) // 1 hour duration
);
event.setDescription(taskData.description);
event.addGuest(taskData.assignedTo);
if (taskData.criticality === 'critical') {
event.addNotification(CalendarApp.NotificationType.POPUP, 0);