Building Real-Time Maintenance Task Notifications: Infrastructure and Architecture for maintenance.queenofsandiego.com
Problem Statement
The maintenance tracking tool at maintenance.queenofsandiego.com had no mechanism to notify team members when new tasks were added. Travis was adding tasks via the UI, but Sergio and other crew members had no visibility into these additions without manually checking the tool. We needed a scalable notification system that could:
- Surface newly added tasks in real-time
- Notify Sergio and team members based on task criticality
- Support both individual notifications and digest formats
- Maintain clear separation between staging and production environments
- Integrate with existing calendar systems for scheduling context
Architecture Overview: Event-Driven Persistence Layer
We implemented a three-tier notification system using Google Apps Script (GAS) as the orchestration layer, AWS Lambda for persistence logic, and email as the delivery mechanism. This approach decouples task creation from notification delivery and allows us to implement intelligent notification rules without modifying the frontend.
Core Components
- MaintenancePersistence.gs - New GAS file handling task persistence and Lambda invocation
- MaintenanceCalendar.gs - New GAS file managing calendar integration and scheduling context
- BookingAutomation.gs - Modified to route
log_maintenanceactions to persistence handlers - staging-index.html - Updated frontend in
/tools/maintenance/staging-index.htmlwith enhanced task logging - Lambda Function - Event processor for task notifications and digest compilation
Technical Implementation Details
Google Apps Script Persistence Layer
We created MaintenancePersistence.gs to intercept maintenance logging actions from the BookingAutomation doPost handler. The handler pattern routes log_maintenance actions:
// In BookingAutomation.gs doPost handler
if (action === 'log_maintenance') {
const persistence = new MaintenancePersistence();
return persistence.logTask(requestData);
}
The persistence layer performs several critical functions:
- Data Validation - Validates task fields (title, description, criticality level, assigned_to)
- Timestamp Recording - Records creation time in ISO 8601 format for accurate audit trails
- Criticality Classification - Categorizes tasks as CRITICAL, HIGH, MEDIUM, or LOW based on content analysis and user input
- Lambda Invocation - Triggers async Lambda function with task payload and notification rules
- Calendar Integration - Queries the "Jada Maintenance" calendar to include scheduling context in notifications
Calendar Context Integration
We created MaintenanceCalendar.gs to query the shared "Jada Maintenance" calendar (jadasailing@gmail.com). This provides crucial context about when maintenance windows are scheduled, allowing us to:
- Route CRITICAL tasks to the on-call crew member immediately
- Batch MEDIUM/LOW tasks into daily digest emails at 7 AM
- Alert the calendar owner (currently Sergio) when new tasks align with scheduled maintenance windows
The calendar query pattern:
const calendarId = 'jadasailing@gmail.com';
const calendar = CalendarApp.getCalendarById(calendarId);
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const events = calendar.getEventsForDay(tomorrow);
Infrastructure: Staging vs. Production Separation
S3 Buckets and CloudFront Distribution
The staging maintenance tool is deployed to:
- S3 Bucket: The live distribution origin (shared infrastructure)
- S3 Path:
/maintenance/staging/forstaging-index.html - CloudFront Distribution: Existing distribution serving maintenance.queenofsandiego.com
- CloudFront Behavior: Route
/staging/*to the staging S3 path prefix
This allows both maintenance.queenofsandiego.com/ (production) and maintenance.queenofsandiego.com/staging/ (staging) to coexist on the same infrastructure without environment variable complexity.
Deployment Commands
// Deploy staging HTML to S3
aws s3 cp staging-index.html s3://[bucket-name]/maintenance/staging/index.html
// Invalidate CloudFront cache for staging path
aws cloudfront create-invalidation --distribution-id [DIST-ID] \
--paths "/staging/*"
// Push GAS changes
clasp push --force
Email Routing and Testing Strategy
For development and staging, all notifications are sent to jadasailing@gmail.com regardless of the assigned crew member. This allows us to test the full notification pipeline without spamming actual crew members during development.
The email logic in Lambda:
- CRITICAL Tasks - Immediate notification with subject: "[CRITICAL] New Maintenance Task: [title]"
- HIGH Tasks - Sent within 1 hour (batched), subject: "[URGENT] Maintenance Updates"
- MEDIUM/LOW Tasks - Daily digest at 7:00 AM, subject: "Daily Maintenance Summary"
The from-address uses an appropriate JADA email alias (to be confirmed with infrastructure team). This maintains professional communication while keeping audit trails clean.
Frontend Changes in staging-index.html
The staging HTML includes enhanced task submission with criticality selection:
- Task title field (required)
- Description textarea (required)
- Criticality dropdown: CRITICAL, HIGH, MEDIUM, LOW
- Assigned crew member selector (auto-populates Sergio by default)
- Enhanced error handling and confirmation messages
Form submission now calls:
fetch('/api/log_maintenance', {
method: 'POST',
body: JSON.stringify({
action: 'log_maintenance',
title: taskTitle,
description: taskDesc,
criticality: selectedLevel,
assigned_to: crewMember,
created_by: currentUser
})
})
Key Architectural Decisions
Why Async Lambda Instead of Synchronous Email
Sending emails synchronously in the GAS script would block the user's task submission and create latency. By invoking Lambda asynchronously, we:
- Return a success response to the user immediately
- Process notifications in parallel without blocking the UI
- Implement retry logic for failed notifications at the Lambda level
- Create an audit trail in CloudWatch Logs for troubleshooting