```html

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_maintenance actions to persistence handlers
  • staging-index.html - Updated frontend in /tools/maintenance/staging-index.html with 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/ for staging-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