```html

Building Real-Time Task Notification for Maintenance.queenofsandiego.com: A Multi-Layer Architecture for Operational Awareness

When Travis added new maintenance tasks via SMS and the system had no way to surface them to Sergio or the team, we faced a classic problem in operational tools: data exists, but visibility doesn't. This post documents how we built a notification system that bridges Google Apps Script, Lambda, S3, CloudFront, and email to create real-time awareness of maintenance updates.

The Problem: Silent Data Entry

The maintenance.queenofsandiego.com tool was accepting task submissions, but new entries weren't being surfaced to stakeholders. Without notification, even critical tasks could languish unnoticed. We needed a solution that:

  • Notifies team members (initially Sergio and CB) when tasks are added
  • Respects task criticality—urgent tasks notify immediately, routine items can digest
  • Works in staging/production parity (currently unified, but architecture supports separation)
  • Integrates with existing Google Workspace infrastructure (GAS, Gmail, Google Calendar)
  • Provides audit trails via the electrical maintenance documentation already in place

Architecture Overview

We implemented a three-tier notification system:

Frontend (staging-index.html)
    ↓
GAS Handler (BookingAutomation.gs - log_maintenance route)
    ↓
Lambda Persistence Layer (async processing)
    ↓
Email Notification (Gmail via SendGrid equivalent or GAS MailApp)
    ↓
Calendar Event (Google Calendar - "Jada Maintenance")
    ↓
Audit Log (electrical/01_work_completed_tonight.md + others)

Technical Implementation

1. Frontend Modification (staging-index.html)

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

We added task submission capture with criticality metadata. When a user adds a task, the form now includes:

  • Task description (existing)
  • Criticality level (new): Critical, High, Medium, Low
  • Assigned to (new): Sergio, CB, Crew
  • Submission timestamp (auto-captured)

The JavaScript modification hooks form submission to POST to the GAS endpoint with action log_maintenance, including the criticality level in the payload.

2. GAS Handler (BookingAutomation.gs)

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

We added a maintenance routing handler to the existing doPost method. When action=log_maintenance:

if (params.action === 'log_maintenance') {
  const criticality = params.criticality || 'medium';
  const assignedTo = params.assigned_to || 'crew';
  
  // Route based on criticality
  if (criticality === 'critical') {
    // Immediate notification
    notifyMaintenance(params, 'immediate');
  } else if (criticality === 'high') {
    // Within 1 hour
    notifyMaintenance(params, 'priority');
  } else {
    // End of day digest
    notifyMaintenance(params, 'digest');
  }
  
  // Persist to Lambda
  callMaintenanceLambda(params);
}

This design follows the principle of "criticality-driven notification pacing"—a pattern validated by high-performing operations teams that reduces notification fatigue while ensuring urgent issues get immediate attention.

3. Persistence Layer (MaintenancePersistence.gs)

Path: /Users/cb/Documents/repos/sites/queenofsandiego.com/MaintenancePersistence.gs

New file that handles async calls to a Lambda function. Instead of blocking the GAS execution with direct database writes, we invoke Lambda asynchronously:

function callMaintenanceLambda(taskData) {
  const lambdaUrl = PropertiesService.getScriptProperties()
    .getProperty('MAINTENANCE_LAMBDA_URL');
  
  const payload = {
    task_id: Utilities.getUuid(),
    description: taskData.description,
    criticality: taskData.criticality,
    assigned_to: taskData.assigned_to,
    submitted_at: new Date().toISOString(),
    submitted_by: Session.getActiveUser().getEmail()
  };
  
  UrlFetchApp.fetch(lambdaUrl, {
    method: 'post',
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  });
}

Why Lambda instead of direct GAS Sheets writes? Lambda provides:

  • Non-blocking execution (GAS POST returns before Lambda finishes)
  • Retry logic if persistence fails
  • Ability to scale independently from GAS quota constraints
  • Structured logging in CloudWatch for audit purposes

4. Notification Handler (MaintenanceCalendar.gs)

Path: /Users/cb/Documents/repos/sites/queenofsandiego.com/MaintenanceCalendar.gs

New file that creates Google Calendar events and sends email notifications. Two notification patterns:

Immediate Notification (Critical tasks):

  • Email sent to Sergio + CB immediately via GmailApp.sendEmail()
  • Calendar event created on "Jada Maintenance" calendar with 15-minute reminder
  • Subject line: [CRITICAL] New Maintenance Task: {description}

Digest Notification (High/Medium/Low tasks):

  • Tasks cached in ScriptProperties
  • Time-based trigger (nightly at 6 PM) aggregates tasks added that day
  • Single consolidated email sent to jadasailing@gmail.com with task list
  • Calendar event created with all tasks in description

Infrastructure Changes

Google Apps Script Deployment

Scripts deployed via clasp to Project ID queenofsandiego-gcp:

clasp push
# Pushes: BookingAutomation.gs, MaintenancePersistence.gs, MaintenanceCalendar.gs

Google Calendar Setup

Created new calendar in jadasailing@gmail.com account:

  • Calendar name: "Jada Maintenance"
  • Sharing: Editor access to sergio@queenofsandiego.com and cb@queenofsandiego.com
  • Notifications: Default 15 minutes for critical events, 1 hour for routine

S3 / CloudFront Updates

Path: s3://maintenance-queenofsandiego-staging/index.html

We deployed the modified staging