Building Real-Time Task Notification Infrastructure for maintenance.queenofsandiego.com
Overview: The Problem
The maintenance tool at maintenance.queenofsandiego.com needed visibility into newly-added tasks. Travis had added tasks via the mobile interface, but without a notification mechanism, the team couldn't discover them reliably. Additionally, Sergio needed to be notified of additions, ideally with notification frequency calibrated to task criticality rather than spamming for every single item.
This required building an end-to-end notification pipeline: capturing task additions in the frontend, persisting them with metadata, triggering notifications based on criticality levels, and delivering them to both staging (for testing) and production channels.
Technical Architecture: Three-Layer Approach
The solution deployed three new components:
- Frontend Capture Layer: Modified
/tools/maintenance/staging-index.htmlto intercept task creation and POST metadata to the backend - GAS Middleware & Persistence: Extended
BookingAutomation.gsrouting and createdMaintenancePersistence.gs+MaintenanceCalendar.gsfor data handling - Notification Dispatcher: Lambda function (deployed separately) consuming task events and routing notifications based on criticality
Frontend Changes: Task Capture
Modified /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/maintenance/staging-index.html to add task submission hooks. The key change was wrapping task creation in a function that:
// Intercepts task form submission
async function submitTaskWithNotification(taskData) {
const taskWithMeta = {
...taskData,
timestamp: new Date().toISOString(),
criticality: determineCriticality(taskData),
addedBy: getCurrentUser(),
notificationRequired: true
};
// Persist locally first (optimistic update)
addTaskToLocalUI(taskWithMeta);
// Send to backend for persistence
const response = await fetch('/log_maintenance', {
method: 'POST',
body: JSON.stringify({
action: 'log_maintenance',
subaction: 'add_task',
payload: taskWithMeta
})
});
return response.json();
}
This approach ensures the UI remains responsive while the backend captures the event for notification triggering.
Google Apps Script Backend: Routing & Persistence
Extended BookingAutomation.gs to route maintenance requests to a dedicated handler:
// In BookingAutomation.gs doPost()
if (request.action === 'log_maintenance') {
return handleMaintenanceRequest(request, parameters);
}
function handleMaintenanceRequest(request, parameters) {
const subaction = request.subaction;
if (subaction === 'add_task') {
return MaintenancePersistence.recordNewTask(request.payload);
} else if (subaction === 'update_task') {
return MaintenancePersistence.updateTask(request.payload);
}
// ... other maintenance actions
}
Created new file MaintenancePersistence.gs to handle data operations:
// MaintenancePersistence.gs
const MAINTENANCE_SHEET = SpreadsheetApp
.openById(JADA_MAINTENANCE_SHEET_ID)
.getSheetByName('Tasks');
function recordNewTask(taskPayload) {
const row = [
new Date().toISOString(),
taskPayload.title,
taskPayload.description,
taskPayload.criticality,
taskPayload.addedBy,
'PENDING',
JSON.stringify(taskPayload)
];
MAINTENANCE_SHEET.appendRow(row);
// Trigger notification workflow
notifyTaskAddition(taskPayload);
return {
success: true,
taskId: Utilities.getUuid(),
timestamp: new Date().toISOString()
};
}
Created MaintenanceCalendar.gs for calendar event creation (future feature for scheduling work):
// MaintenanceCalendar.gs
function syncTaskToCalendar(taskPayload) {
const calendar = CalendarApp.getCalendarById(
'Jada Maintenance'
);
if (taskPayload.scheduledDate) {
calendar.createEvent(
taskPayload.title,
new Date(taskPayload.scheduledDate),
new Date(taskPayload.scheduledDate),
{description: taskPayload.description}
);
}
}
Infrastructure: S3, CloudFront, and Email Routing
S3 Deployment: The staging maintenance HTML was deployed to the origin S3 bucket (identified via CloudFront origin inspection) at the path corresponding to the CloudFront distribution. CloudFront cache was invalidated immediately:
aws s3 cp staging-index.html s3://[BUCKET]/maintenance/index.html
aws cloudfront create-invalidation \
--distribution-id [DIST_ID] \
--paths "/maintenance/*"
GAS Deployment: Used clasp to push the new files:
clasp push --force
# Ensures MaintenancePersistence.gs and MaintenanceCalendar.gs
# are tracked and deployed to the GAS project
Key Decision: Notification Frequency Based on Criticality
Rather than spamming notifications for every task, we implemented a criticality-based dispatcher:
- CRITICAL (e.g., safety hazards, electrical faults): Immediate email to Sergio + ops channel
- HIGH (e.g., equipment damage, system down): Within 1 hour (batched)
- MEDIUM: Daily digest at 5 PM
- LOW: Weekly digest
This approach is backed by incident response best practices—PagerDuty, Opsgenie, and similar platforms use this exact model. Immediate notification for critical items prevents false urgency fatigue while ensuring critical issues surface immediately.
Staging vs. Production: For testing, notifications are sent to jadasailing@gmail.com with a [STAGING] prefix in the subject line. In production, Sergio's primary email and ops channels receive the messages. This is configured via environment-specific routing in the Lambda function.
What's Next
- Mobile Push Notifications: Add Firebase Cloud Messaging for real-time alerts on Sergio's phone
- Task Assignment Workflow: When a task is created, automatically assign based on skill tags and availability
- Completion Tracking: Capture task completion timestamps and duration metadata for maintenance analytics
- Integration with Stripe: Link maintenance tasks to charter cancellations or customer refunds for financial impact analysis
- Historical Analytics: Build reporting dashboard showing maintenance frequency, average resolution time, and cost per hour of downtime
The foundation is now in place for real-time visibility