Building a Real-Time Maintenance Task Notification System for maintenance.queenofsandiego.com

The Problem

JADA's maintenance tracking tool at maintenance.queenofsandiego.com was receiving task additions via SMS and manual data entry, but there was no visible mechanism to surface newly added tasks or notify the operations team when work had been logged. With Travis adding tasks and Sergio needing visibility into new maintenance items, the system required a real-time notification layer that could intelligently batch or prioritize notifications based on task criticality.

What We Built

This session implemented a three-tier notification architecture:

  • AWS Lambda persistence layer — async task logging without blocking the UI
  • Google Apps Script handlers — intelligent notification routing with criticality-based delivery
  • Frontend task surfacing — immediate visual feedback in the maintenance tool UI
  • Email digest system — daily rollup of non-critical tasks with immediate alerts for critical items
  • Google Calendar integration — automatic creation of calendar events for high-priority maintenance

Technical Architecture

1. AWS Lambda Persistence Layer

We created MaintenancePersistence.gs as a Google Apps Script wrapper that handles Lambda invocation. The Lambda function receives task payloads and persists them to DynamoDB, ensuring that even if the GAS notification tier is slow or fails, task data is captured immediately.

// MaintenancePersistence.gs structure
function logMaintenanceTaskToLambda(taskData) {
  const lambdaEndpoint = PropertiesService.getScriptProperties()
    .getProperty('MAINTENANCE_LAMBDA_ENDPOINT');
  
  const payload = {
    taskId: taskData.id,
    description: taskData.description,
    criticality: taskData.criticality, // 'low', 'medium', 'high', 'critical'
    loggedBy: taskData.loggedBy,
    timestamp: new Date().toISOString(),
    assignedTo: taskData.assignedTo || 'unassigned'
  };
  
  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
    muteHttpExceptions: true,
    timeout: 10
  };
  
  return UrlFetchApp.fetch(lambdaEndpoint, options);
}

The Lambda function (deployed via CloudFormation) writes to a DynamoDB table with a TTL of 90 days, ensuring task history is retained for reporting but doesn't accumulate indefinitely.

2. GAS Notification Routing in BookingAutomation.gs

We added a new route handler to the existing BookingAutomation.gs doPost method that intercepts action: 'log_maintenance' requests. The handler implements criticality-based notification logic:

  • Critical tasks (e.g., electrical hazards, safety issues) — immediate email + SMS to Sergio + calendar event
  • High-priority tasks (e.g., equipment failures) — immediate email, added to daily digest
  • Medium/Low tasks — batched in daily 6 PM digest email
// Route added to BookingAutomation.gs doPost
if (params.action === 'log_maintenance') {
  const taskData = {
    id: Utilities.getUuid(),
    description: params.description,
    criticality: params.criticality || 'medium',
    loggedBy: params.loggedBy,
    assignedTo: params.assignedTo || null,
    createdAt: new Date()
  };
  
  // Persist to Lambda immediately (async, fire-and-forget)
  MaintenancePersistence.logMaintenanceTaskToLambda(taskData);
  
  // Route notification based on criticality
  if (taskData.criticality === 'critical') {
    notifyCriticalTask(taskData);
  } else if (taskData.criticality === 'high') {
    notifyHighPriorityTask(taskData);
  } else {
    queueForDailyDigest(taskData);
  }
  
  return ContentService.createTextOutput(
    JSON.stringify({status: 'logged', taskId: taskData.id})
  ).setMimeType(ContentService.MimeType.JSON);
}

3. Calendar Integration with MaintenanceCalendar.gs

New file MaintenanceCalendar.gs handles automatic calendar event creation. For critical and high-priority tasks, it creates events on the "Jada Maintenance" calendar associated with jadasailing@gmail.com. This gives Sergio a native calendar view of upcoming maintenance work.

// MaintenanceCalendar.gs
function createMaintenanceCalendarEvent(taskData) {
  const calendar = CalendarApp.getCalendarById('jadasailing@gmail.com');
  
  if (!calendar) {
    Logger.log('Jada Maintenance calendar not found');
    return;
  }
  
  const eventTitle = `[${taskData.criticality.toUpperCase()}] ${taskData.description}`;
  const startTime = new Date();
  const endTime = new Date(startTime.getTime() + 60 * 60 * 1000); // 1 hour
  
  if (taskData.criticality === 'critical') {
    // Create immediate event
    calendar.createEvent(eventTitle, startTime, endTime, {
      description: `Logged by: ${taskData.loggedBy}\nTask ID: ${taskData.id}`,
      guests: 'sergio@jada.local'
    });
  } else if (taskData.criticality === 'high') {
    // Create event for next business day 9 AM
    const tomorrow = new Date(startTime);
    tomorrow.setDate(tomorrow.getDate() + 1);
    tomorrow.setHours(9, 0, 0, 0);
    
    calendar.createEvent(eventTitle, tomorrow, 
      new Date(tomorrow.getTime() + 60 * 60 * 1000));
  }
}

Frontend Changes to staging-index.html

The maintenance tool UI now includes:

  • A "New Tasks" banner that appears when fresh tasks are logged (real-time via polling)
  • A task criticality selector in the form (dropdown: low/medium/high/critical)
  • Real-time task list with color coding based on criticality
  • Immediate visual feedback when a task is submitted

The staging version is deployed to https://maintenance-staging.queenofsandiego.com via S3 bucket with CloudFront acceleration. We added a task polling mechanism that queries the Lambda endpoint every 30 seconds to check for newly added tasks without user action.

Infrastructure Components

S3 Buckets:

  • queenofsandiego-maintenance-staging — staging HTML/JS artifacts
  • queenofsandiego-maintenance-live — production maintenance tool

CloudFront Distributions:

  • Staging: d-maintenance-staging.queenofsandiego.com (invalidation command: aws cloudfront create-invalidation --