Implementing Real-Time Task Notifications for maintenance.queenofsandiego.com: A Multi-Layer Notification Architecture
The Queen of San Diego maintenance tool needed a critical capability: surfacing new tasks added by crew members and notifying stakeholders in real-time. This post details the architectural decisions, implementation approach, and infrastructure changes deployed to staging for the maintenance.queenofsandiego.com application.
The Problem
Crew members like Travis were adding maintenance tasks to the system, but the notification mechanism was non-existent. This created a visibility gap where Sergio (team lead) and other stakeholders wouldn't know about new work items. We needed a solution that:
- Surfaced new tasks immediately in the UI
- Notified relevant stakeholders via email
- Scaled intelligently based on task criticality (not spam for low-priority items)
- Maintained staging/production separation despite shared infrastructure
Architecture Overview
The solution implements a three-tier notification system:
- Frontend Layer: Modified HTML/JS in staging-index.html to poll for new tasks and surface them in the UI
- Backend Persistence Layer: New MaintenancePersistence.gs module handling task writes and change tracking
- Notification Layer: Lambda function processing task changes and triggering email notifications based on criticality rules
Implementation Details
Frontend: Task Discovery UI (staging-index.html)
The staging maintenance tool located at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/maintenance/staging-index.html was modified to:
- Implement a polling mechanism checking for new tasks every 30 seconds
- Compare local task state against the backend to identify additions
- Display a visual indicator (badge or alert) when new tasks are detected
- Persist the "last checked" timestamp in browser localStorage to avoid redundant notifications
The key initialization happens in the init() function, which now bootstraps the task polling listener alongside existing maintenance display logic. This approach avoids blocking the initial page load while ensuring timely discovery of additions.
Google Apps Script Persistence Layer (MaintenancePersistence.gs)
A new GAS module was created at /Users/cb/Documents/repos/sites/queenofsandiego.com/MaintenancePersistence.gs to handle:
- Task write operations with timestamp tracking
- Change detection by comparing against previous state
- Metadata enrichment (who added it, when, criticality level)
- Integration with the existing BookingAutomation.gs routing
The module exposes a logMaintenanceTask(taskData) function that:
- Accepts task data including criticality level (critical, high, medium, low)
- Writes to Google Sheets (the current persistence layer for this application)
- Returns structured metadata about the change that triggers downstream processing
Integration was added to BookingAutomation.gs doPost handler, routing action=log_maintenance requests to the new persistence module. This maintains the existing request routing pattern used throughout the application.
Action Routing in BookingAutomation.gs
The doPost handler in BookingAutomation.gs was modified to add:
if (params.action === 'log_maintenance') {
const result = MaintenancePersistence.logMaintenanceTask(params);
return ContentService.createTextOutput(
JSON.stringify(result)
).setMimeType(ContentService.MimeType.JSON);
}
This approach keeps all HTTP routing centralized, making the codebase easier to maintain as new features are added.
Infrastructure: Lambda Notification Processing
A Lambda function was deployed to handle asynchronous email notifications. This separation is critical because:
- Performance: Email delivery doesn't block the web request
- Reliability: Failed emails can be retried independently
- Scalability: Email batching and rate limiting live outside the synchronous path
The Lambda function is configured with:
- Runtime: Node.js 20.x
- IAM Role: Referenced the existing
tips-boxLambda deployment pattern for role configuration, ensuring proper permissions for SES (Simple Email Service) and CloudWatch Logs - Environment variables: Staging environment flag to route test notifications to
jadasailing@gmail.com
The function implements criticality-based routing:
- Critical/High Priority: Immediate email notification to Sergio
- Medium Priority: Added to a daily digest sent at end-of-day (9 PM PT)
- Low Priority: Logged to CloudWatch; accessible via dashboard but no active notification
Staging vs. Production Separation
Since both staging and production share the same CloudFront distribution for maintenance.queenofsandiego.com, we implemented separation at the application layer:
- Staging HTML includes a flag in localStorage:
IS_STAGING_ENVIRONMENT=true - All task submissions in staging include a prefix:
STAGING_in the task identifier - Email notifications check this prefix and route accordingly
- Test emails go to
jadasailing@gmail.com(a team email alias) rather than individual crew members
The CloudFront distribution remains unified at d[distribution-id].cloudfront.net, but the index.html file is environment-aware. Production deployments would update the HTML directly rather than through staging paths.
Deployment Process
Changes were deployed in the following order:
- Created and pushed
MaintenancePersistence.gsto Google Apps Script - Updated
BookingAutomation.gswith action routing, pushed via clasp - Modified
tools/maintenance/staging-index.htmlwith polling logic and staging flag initialization - Deployed staging HTML to S3 bucket and invalidated CloudFront cache
- Lambda function packaged and deployed with proper environment configuration
CloudFront cache invalidation used the wildcard pattern to ensure staging changes were immediately visible:
aws cloudfront create-invalidation \
--distribution-id [DIST_ID] \
--paths "/maintenance/*"
Key Decisions and Rationale
- Polling over WebSockets: Reduces infrastructure complexity. A 30-second poll frequency balances latency (crew members see new tasks within half a minute) against load (288 requests per crew member per day).
- Criticality-Based Notifications: Following industry best practices from high-performing incident response teams, we avoid alert fatigue by routing low-priority notifications to dashboards rather than inboxes. This maintains engagement with critical alerts.
- Lambda for Email: Decouples email