Building a Real-Time Task Notification System for maintenance.queenofsandiego.com
When Travis added tasks to the maintenance tool via SMS and they didn't appear in any obvious way, we realized the system needed a proper notification mechanism. This post details how we implemented real-time task change detection, asynchronous notification delivery, and staging/production separation for a Google Apps Script-based maintenance tracking system.
The Problem: Task Visibility and Team Awareness
The maintenance.queenofsandiego.com tool had a critical gap: when new maintenance tasks were added, there was no way for the team (particularly Sergio, our operations lead) to know immediately. The existing system stored tasks but provided no alerting mechanism. We needed to:
- Surface newly added tasks in real-time
- Notify team members asynchronously without blocking the UI
- Determine notification cadence based on task criticality (immediate vs. daily digest)
- Maintain separate staging and production notification flows
- Integrate with existing Google Calendar infrastructure for event tracking
Architecture: Lambda + GAS + Email Persistence Pattern
We implemented a three-tier notification architecture:
- Persistence Layer (Lambda): Serverless function to store task change events and manage notification state
- GAS Handler: Google Apps Script function to receive task submissions and trigger notifications
- Email Delivery: Gmail integration for immediate and digest notifications
This pattern isolates notification logic from the real-time UI path, ensuring task creation never waits for email delivery.
Implementation Details
File: /Users/cb/Documents/repos/sites/queenofsandiego.com/MaintenancePersistence.gs
We created a new GAS file to handle persistent storage of maintenance events and notification state. This file manages:
- Writing task events to a CloudFirestore-backed database (via Lambda)
- Tracking which tasks have been notified
- Managing notification timestamps for digest logic
- Querying recent changes for the staging vs. production email destinations
The Lambda integration allows us to persist data outside of Google's limited GAS storage, giving us reliable audit trails and notification history.
File: /Users/cb/Documents/repos/sites/queenofsandiego.com/MaintenanceCalendar.gs
A new companion file for calendar operations. This handles:
- Creating or fetching the "Jada Maintenance" calendar in
jadasailing@gmail.com - Parsing task criticality from descriptions
- Creating calendar events for high-priority maintenance items
- Updating calendar event descriptions with task completion status
We use Google Calendar as a secondary notification medium for critical tasks, ensuring nothing slips through email filters.
File: /Users/cb/Documents/repos/sites/queenofsandiego.com/BookingAutomation.gs
Updated the existing doPost handler to route maintenance task submissions to a new action handler:
if (data.action === 'log_maintenance') {
return handleMaintenanceLog(data);
}
The handleMaintenanceLog function:
- Validates task data (title, description, criticality level)
- Calls the persistence Lambda with task details
- Immediately returns success to the UI (async pattern)
- Triggers notification dispatch asynchronously
File: /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/maintenance/staging-index.html
The staging maintenance tool HTML was updated with:
- Task criticality selector (Low / Medium / High / Critical)
- Form validation for required fields
- POST handler that routes to the GAS
doPostendpoint withaction=log_maintenance - Client-side environment detection to target staging vs. production endpoints
We added a feature flag in the initialization to send all test notifications to jadasailing@gmail.com while in staging mode, allowing us to test the full notification pipeline without spamming production recipients.
Infrastructure: Lambda Function Deployment
The Lambda function (deployment pattern modeled after existing tips-box Lambda) is configured as:
- Runtime: Python 3.11
- Handler:
index.lambda_handler - IAM Role: Uses existing service role with CloudFirestore and Secrets Manager permissions
- Timeout: 30 seconds (accounts for potential database latency)
- Memory: 256 MB
The Lambda function:
- Receives task payload from GAS via HTTPS POST
- Stores event in CloudFirestore collection:
/maintenance_tasks/{task_id} - Updates notification state collection:
/notification_state/{date} - Returns JSON response with task ID and persistence confirmation
- Handles failures gracefully with exponential backoff retry logic
Notification Logic: Criticality-Driven Cadence
Industry best practices (informed by analysis of SRE team workflows from Google Cloud documentation and incident management research) suggest:
- Critical tasks: Immediate email notification + calendar event + SMS (future)
- High priority: Immediate email notification + calendar event
- Medium priority: Added to daily digest, sent at 8 AM
- Low priority: Weekly digest only
This approach prevents alert fatigue while ensuring urgent items get immediate visibility. The daily digest consolidation for medium/low tasks aligns with how high-performing ops teams (per Accelerate: The Science of Lean Software and DevOps) operate — batching non-urgent items to reduce context switching.
Staging vs. Production Separation
Since maintenance.queenofsandiego.com shares infrastructure with the live tool, we implemented separation via:
- CloudFront staging distribution: Points to
staging-index.htmlon S3 bucketqueenofsandiego-maintenance-staging - GAS environment variable: Set via
PropertiesService.getScriptProperties()['ENVIRONMENT'] - Lambda conditional: Routes staging requests to test SNS topic (eventually email), production to primary topic
- Email routing: Test emails go to
jadasailing@gmail.com; production notifications route to team distribution list
This separation allows full integration testing without risking production notification spam.
Deployment Steps
The deployment sequence ensures zero downtime:
- Create new GAS files:
MaintenancePersistence.gs,