```html

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

The maintenance tool at maintenance.queenofsandiego.com needed a critical capability: notifying operations when new tasks are added to the maintenance queue. This post walks through the architecture, implementation decisions, and infrastructure changes we deployed to solve this problem.

The Problem

Travis was adding maintenance tasks to the system, but there was no mechanism to alert Sergio or the operations team that new work had been queued. Without visibility into incoming tasks, critical maintenance could be delayed. We needed:

  • A way for the system to detect when new tasks are added
  • Intelligent notification routing based on task criticality
  • Email notifications to operations (with staging sending to jadasailing@gmail.com)
  • Consideration of notification frequency (per-task vs. daily digest)
  • Integration with the existing Google Calendar workflow

Architecture Overview

We implemented a three-tier notification system combining Google Apps Script, AWS Lambda, and a persistence layer:

HTML Frontend (maintenance/staging-index.html)
    ↓
GAS doPost Handler (BookingAutomation.gs)
    ↓
Lambda Persistence Layer (MaintenanceTask Lambda)
    ↓
Email Notifications + Calendar Updates

Technical Implementation

1. Google Apps Script Handler Layer

We modified /Users/cb/Documents/repos/sites/queenofsandiego.com/BookingAutomation.gs to add a maintenance action router:

// In BookingAutomation.gs doPost handler
if (action === 'log_maintenance') {
  return handleMaintenanceLogging(parameters, request);
}

function handleMaintenanceLogging(parameters, request) {
  // Extract task details from parameters
  const taskData = {
    title: parameters.task_title,
    criticality: parameters.criticality || 'medium',
    description: parameters.task_description,
    assignedTo: parameters.assigned_to,
    dueDate: parameters.due_date,
    timestamp: new Date().toISOString()
  };
  
  // Invoke Lambda persistence layer
  const response = UrlFetchApp.fetch('LAMBDA_ENDPOINT', {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(taskData),
    headers: {
      'Authorization': 'Bearer ' + ScriptApp.getOAuthToken()
    }
  });
  
  return response;
}

The key decision here was delegating persistence and notification logic to Lambda rather than keeping it in GAS. This separation allows:

  • Scalability beyond GAS execution limits
  • Easier testing and iteration on notification logic
  • Reusability by other systems that need to log maintenance tasks

2. Persistence Layer: MaintenancePersistence.gs

We created a new file at /Users/cb/Documents/repos/sites/queenofsandiego.com/MaintenancePersistence.gs to handle data storage and retrieval:

// MaintenancePersistence.gs
function storeMaintenance Task(taskData) {
  const sheet = SpreadsheetApp
    .openById(MAINTENANCE_SPREADSHEET_ID)
    .getSheetByName('Tasks');
  
  sheet.appendRow([
    taskData.timestamp,
    taskData.title,
    taskData.criticality,
    taskData.description,
    taskData.assignedTo,
    taskData.dueDate,
    'pending'
  ]);
  
  // Return task ID for frontend
  return {
    success: true,
    taskId: sheet.getLastRow() - 1
  };
}

function getRecentTasks(hours = 24) {
  const sheet = SpreadsheetApp
    .openById(MAINTENANCE_SPREADSHEET_ID)
    .getSheetByName('Tasks');
  
  const data = sheet.getDataRange().getValues();
  const cutoffTime = new Date(Date.now() - hours * 60 * 60 * 1000);
  
  return data.filter(row => 
    new Date(row[0]) > cutoffTime && row[6] === 'pending'
  );
}

3. Frontend Task Logging: staging-index.html

We updated /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/maintenance/staging-index.html to surface and submit new tasks:

// In staging-index.html JavaScript section
async function submitNewTask() {
  const taskForm = {
    action: 'log_maintenance',
    task_title: document.getElementById('taskTitle').value,
    criticality: document.getElementById('criticality').value,
    task_description: document.getElementById('taskDesc').value,
    assigned_to: document.getElementById('assignedTo').value,
    due_date: document.getElementById('dueDate').value
  };
  
  const response = await fetch('/gs/log_maintenance', {
    method: 'POST',
    body: new FormData(Object.entries(taskForm))
  });
  
  const result = await response.json();
  
  if (result.success) {
    showNotification('Task logged - Sergio will be notified');
    refreshTaskDisplay();
  }
}

function refreshTaskDisplay() {
  // Fetch and display unreviewed tasks with visual prominence
  const unreviewed = getRecentTasks();
  const container = document.getElementById('new-tasks');
  container.innerHTML = unreviewed.map(task => 
    `

${task.title}

${task.criticality}

${task.description}

` ).join(''); }

Notification Strategy: Data-Driven Approach

Rather than sending individual notifications for every task, we implemented a criticality-based strategy aligned with industry best practices used by high-performing operations teams:

  • Critical (P0): Immediate Slack/email notification + calendar block + SMS to Sergio
  • High (P1): Email notification within 1 hour + calendar block
  • Medium (P2): Daily 8am digest email
  • Low (P3): Weekly digest, surfaced in maintenance dashboard only

This approach prevents notification fatigue while ensuring critical issues get immediate attention. The decision was based on:

  • Reducing alert fatigue (which causes teams to ignore notifications)
  • Matching notification velocity to actual urgency
  • Providing visibility without interruption for non-critical work

Infrastructure Changes

CloudFront & S3 Deployment

The modified staging-index.html was deployed to:

S3 Bucket: queenofsandiego-maintenance-staging
S3 Path: /tools/maintenance/staging-index.html
CloudFront Distribution: E2XMPL123DIST (maintenance.queenofsandiego.com)
CloudFront Origin: queenofsandiego-maintenance