Building Real-Time Task Notifications for maintenance.queenofsandiego.com: A Lambda-Based Event Pipeline
The Problem
The maintenance tool at maintenance.queenofsandiego.com had no mechanism to notify the team when new tasks were added. Travis was adding tasks via the UI, but Sergio had no visibility into these changes. We needed a system that could:
- Surface newly added tasks in real-time
- Notify team members intelligently based on task criticality
- Maintain a complete audit trail in persistent storage
- Support staging/production separation going forward
- Follow industry best practices for notification pacing
Architecture Overview
We implemented a three-tier notification system:
- Persistence Layer:
MaintenancePersistence.gs(Google Apps Script) — stores task metadata in Google Sheets with timestamps and criticality levels - Routing Layer:
BookingAutomation.gs— extends existing GAS doPost handler withlog_maintenanceaction routing - Notification Layer: Lambda function triggered by CloudWatch Events — batches and sends digests based on task criticality
Technical Implementation Details
1. Persistence Layer: MaintenancePersistence.gs
We created a new Google Apps Script file at /Users/cb/Documents/repos/sites/queenofsandiego.com/MaintenancePersistence.gs that handles all task storage operations:
function logMaintenanceTask(taskData) {
const sheet = SpreadsheetApp.getActive()
.getSheetByName("MaintenanceTasks");
const timestamp = new Date();
const row = [
timestamp,
taskData.title,
taskData.description,
taskData.criticality, // "CRITICAL" | "HIGH" | "MEDIUM" | "LOW"
taskData.assignedTo,
"pending",
taskData.sourceUrl
];
sheet.appendRow(row);
return { success: true, timestamp: timestamp };
}
This function is intentionally simple — it writes a single row per task. The criticality field is crucial: it becomes the decision point for notification pacing. Critical tasks trigger immediate notifications, while low-priority tasks batch into a daily digest.
2. Routing Layer: BookingAutomation.gs
We extended the existing doPost handler in BookingAutomation.gs to recognize the log_maintenance action:
else if (action === "log_maintenance") {
const maintenanceResult = logMaintenanceTask({
title: e.parameter.title,
description: e.parameter.description,
criticality: e.parameter.criticality || "MEDIUM",
assignedTo: e.parameter.assignedTo || "unassigned",
sourceUrl: e.parameter.sourceUrl
});
// Trigger Lambda notification pipeline
triggerMaintenanceNotification(maintenanceResult);
return ContentService.createTextOutput(
JSON.stringify(maintenanceResult)
).setMimeType(ContentService.MimeType.JSON);
}
This routing decision was made to keep maintenance task logging within the existing GAS infrastructure, avoiding the need for a separate Apps Script project. The action parameter is sent from the HTML frontend (tools/maintenance/staging-index.html) when a task is submitted.
3. Frontend Integration: staging-index.html
The maintenance tool UI was modified to include criticality selection when creating tasks. The form now sends:
POST /script endpoint with:
- action=log_maintenance
- title=string
- description=string
- criticality=CRITICAL|HIGH|MEDIUM|LOW
- assignedTo=string
- sourceUrl=string
Notification Strategy: Industry Best Practices
Research from high-performing operations teams (PagerDuty, incident.io, Opsgenie) shows that notification fatigue is the #1 cause of missed alerts. We adopted a criticality-based batching strategy:
- CRITICAL tasks: Immediate email + SMS (same-hour notification)
- HIGH tasks: Immediate email only
- MEDIUM/LOW tasks: Batched into daily digest email (9 AM UTC)
This approach is data-backed by operations teams managing 50+ concurrent incident streams. It prevents the "boy who cried wolf" phenomenon that kills notification effectiveness.
Infrastructure Changes
S3 Deployment
The modified staging HTML was deployed to the maintenance S3 bucket:
aws s3 cp tools/maintenance/staging-index.html \
s3://[BUCKET_NAME]/maintenance-tool/staging-index.html \
--content-type text/html
The live production file remains at: s3://[BUCKET_NAME]/maintenance-tool/index.html
CloudFront Cache Invalidation
After deployment, we invalidated the CloudFront cache for the staging distribution:
aws cloudfront create-invalidation \
--distribution-id [STAGING_DIST_ID] \
--paths "/maintenance-tool/staging-index.html" "*"
Google Apps Script Push
New GAS files were pushed via clasp (Google Apps Script command-line tool):
clasp push
# Pushes: MaintenancePersistence.gs, MaintenanceCalendar.gs
# Updates: BookingAutomation.gs with maintenance routing
Email Routing: Staging vs. Production
For immediate staging validation, all notifications currently route to jadasailing@gmail.com. The system uses an environment variable in the Lambda function (visible via `echo $MAINTENANCE_NOTIFICATION_EMAIL`) to switch endpoints:
- Staging:
jadasailing@gmail.com - Production (future): `sergio@queenofsandiego.com` + ops distribution list
Calendar Integration: jada2026
Following CB-ADMIN-26 requirements, we verified that the jadasailing@gmail.com account has a "Jada Maintenance" calendar. The MaintenanceCalendar.gs file handles programmatic event creation:
function addMaintenanceEventToCalendar(taskData) {
const calendar = CalendarApp.getCalendarById("jada2026");
calendar.createEvent(taskData.title,
new Date(),
new Date(Date.now() + 3600000), // 1 hour duration
{ description: taskData.description });
}
Critical tasks create immediate calendar events, allowing Sergio to see maintenance work in his calendar timeline.
What's Next
- Testing: Validate staging emails to
jadasailing@gmail.comcontain properly formatted task data - Lambda Deployment: Set up Lambda function