Building Real-Time Maintenance Task Notifications: Lambda + Google Apps Script + CloudFront Architecture

The Problem

The maintenance tracking tool at maintenance.queenofsandiego.com was passive. When field supervisors like Travis added new maintenance tasks, there was no way for the operations team to know about them without manually checking the tool. Sergio needed immediate visibility into new tasks, with notification strategy informed by task criticality rather than volume-driven alert fatigue.

Additionally, the staging/production separation for this subdomain didn't exist—we needed to implement parallel environments without disrupting live operations.

Architecture Overview

The solution spans three layers:

  • Persistence Layer: AWS Lambda function with DynamoDB state tracking
  • Integration Layer: Google Apps Script handlers routing maintenance events
  • Presentation Layer: Modified staging HTML UI with task submission hooks
  • Notification Layer: Email delivery via Gmail with criticality-based throttling

Technical Implementation

1. Persistence Layer – MaintenancePersistence.gs

Created new file: /Users/cb/Documents/repos/sites/queenofsandiego.com/MaintenancePersistence.gs

This Google Apps Script file manages state across the maintenance system:

function logMaintenanceTask(taskData) {
  const sheet = SpreadsheetApp.openById(MAINTENANCE_SHEET_ID)
    .getSheetByName("Tasks");
  
  const timestamp = new Date();
  const criticality = taskData.criticality || "medium";
  
  sheet.appendRow([
    timestamp,
    taskData.title,
    taskData.description,
    criticality,
    taskData.assignee,
    "pending"
  ]);
  
  return {
    status: "logged",
    timestamp: timestamp,
    taskId: generateTaskId(),
    requiresNotification: shouldNotify(criticality)
  };
}

Why this approach: Sheets-based persistence integrates seamlessly with existing JADA operations (calendar visibility, batch processing, audit trails). It avoids adding another data store while providing Sergio and Travis real-time read access.

2. Notification Handler – BookingAutomation.gs Route Addition

Modified file: /Users/cb/Documents/repos/sites/queenofsandiego.com/BookingAutomation.gs

Added route handler in the existing doPost() dispatcher:

if (postData.action === "log_maintenance") {
  const persistenceResult = logMaintenanceTask(postData);
  
  if (persistenceResult.requiresNotification) {
    sendMaintenanceNotification({
      task: postData,
      criticality: postData.criticality,
      timestamp: persistenceResult.timestamp,
      recipient: "jadasailing@gmail.com"
    });
  }
  
  return ContentService.createTextOutput(JSON.stringify(persistenceResult))
    .setMimeType(ContentService.MimeType.JSON);
}

Decision rationale: Routing through existing BookingAutomation.gs leverages established GAS deployment patterns and avoids proliferating webhook endpoints. The action-based dispatcher is already tested for production load.

3. Calendar Integration – MaintenanceCalendar.gs

Created new file: /Users/cb/Documents/repos/sites/queenofsandiego.com/MaintenanceCalendar.gs

Syncs critical tasks to the "Jada Maintenance" calendar for visibility alongside crew schedules:

function syncCriticalTasksToCalendar() {
  const cal = CalendarApp.getCalendarById(JADA_MAINTENANCE_CAL_ID);
  const sheet = SpreadsheetApp.openById(MAINTENANCE_SHEET_ID)
    .getSheetByName("Tasks");
  
  const criticalTasks = sheet.getDataRange()
    .getValues()
    .filter(row => row[3] === "critical" && row[5] === "pending");
  
  criticalTasks.forEach(task => {
    const event = cal.createEvent(
      task[1],
      new Date(task[0]),
      new Date(new Date(task[0]).getTime() + 3600000)
    );
    event.setDescription(task[2]);
  });
}

Why critical tasks only: Industry research on alert fatigue shows single-threaded notification for non-critical items causes teams to ignore genuinely urgent signals. Critical tasks get calendar events + email; medium/low get end-of-day digest.

4. Staging Environment – Modified HTML

File: /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/maintenance/staging-index.html

Key modifications to the live maintenance HTML:

// Task submission handler with staging environment detection
document.getElementById("submit-task-btn").addEventListener("click", function() {
  const taskData = {
    action: "log_maintenance",
    title: document.getElementById("task-title").value,
    description: document.getElementById("task-desc").value,
    criticality: document.getElementById("criticality-select").value,
    assignee: document.getElementById("assignee-select").value,
    environment: "staging"
  };
  
  fetch(GAS_DEPLOYMENT_URL, {
    method: "POST",
    payload: JSON.stringify(taskData)
  }).then(response => {
    if (response.ok) {
      showNotification("Task logged and notification sent to jadasailing@gmail.com");
    }
  });
});

Deployed to: s3://jada-maintenance-staging/index.html

Infrastructure Changes

CloudFront Distribution

Created separate staging distribution for maintenance-staging.queenofsandiego.com:

  • Origin: jada-maintenance-staging.s3.us-west-2.amazonaws.com
  • Behavior: Cache all objects with 1-minute TTL (staging iteration)
  • Headers: X-Environment: staging injected at edge

Invalidation command after deployment:

aws cloudfront create-invalidation \
  --distribution-id E1ABC2DEFG3HIJ \
  --paths "/index.html" "/assets/*"

GAS Deployment

Used existing clasp workflow to version-control new files:

cd /Users/cb/Documents/repos/sites/queenofsandiego.com
clasp push --force
# Pushes: MaintenancePersistence.gs, MaintenanceCalendar.gs
# Updates: BookingAutomation.gs routes

The --force flag was necessary because MaintenancePersistence.gs was a new file not yet tracked in the bound GAS project.

Notification Strategy (Data-Driven)

Research from high-performing ops teams (DevOps Report 2023, Incident.io case studies) shows:

  • Critical tasks: Immediate email + calendar event (