```html

Building a Task Notification System for maintenance.queenofsandiego.com: Real-time Alerting with Google Apps Script and Lambda

What We Built

The maintenance tool at maintenance.queenofsandiego.com needed a critical feature: when Travis or other team members add tasks, Sergio and the operations team should know immediately—without constantly refreshing the page. We implemented a multi-layer notification system that:

  • Persists new task data to AWS Lambda via HTTP callbacks
  • Routes notifications through Google Apps Script to email alerts
  • Intelligently determines notification frequency based on task criticality
  • Separates staging and production through environment-aware email routing
  • Maintains audit logs in a dedicated GAS file for troubleshooting

Architecture Overview

The system spans three distinct layers:

  • Frontend (staging-index.html): Modified maintenance tool UI that captures task submissions and POSTs to a backend endpoint
  • Google Apps Script Layer: New MaintenancePersistence.gs file that acts as the notification coordinator, with routes added to BookingAutomation.gs
  • AWS Lambda + S3: Persistence layer that durably stores task events and maintains notification history

Technical Implementation Details

Frontend: Modified staging-index.html

The staging maintenance tool lives at: /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/maintenance/staging-index.html

Key JavaScript additions capture task creation events:

// When a task is submitted, immediately post to the GAS endpoint
const taskPayload = {
  action: 'log_maintenance',
  task_name: taskElement.dataset.taskName,
  criticality: taskElement.dataset.criticality,
  assigned_to: taskElement.dataset.assignedTo,
  timestamp: new Date().toISOString(),
  source: 'maintenance_tool'
};

fetch(GAS_DEPLOYMENT_URL, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(taskPayload)
})
.catch(err => console.error('Task log failed:', err));

This ensures we capture tasks in real-time, even if the user closes their browser immediately after entering data.

Google Apps Script: New MaintenancePersistence.gs

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

This file handles the core notification logic:

function handleMaintenanceLog(payload) {
  // Determine notification urgency based on criticality
  const criticality = payload.criticality || 'normal';
  const shouldNotifyImmediate = criticality === 'critical' || criticality === 'urgent';
  
  // Store task in Lambda for durability
  persistToLambda(payload);
  
  // Route notifications based on environment
  const recipientEmail = isProduction(payload) 
    ? 'sergio@...com' 
    : 'jadasailing@gmail.com';
  
  if (shouldNotifyImmediate) {
    sendImmediateAlert(recipientEmail, payload);
  } else {
    queueForDigest(payload);
  }
  
  logAuditTrail(payload);
}

The criticality-based routing follows industry best practices from high-performing ops teams: critical tasks get immediate notifications, while routine tasks batch into daily digests. This reduces alert fatigue while maintaining responsiveness for true emergencies.

Route Integration in BookingAutomation.gs

The existing doPost handler in BookingAutomation.gs was extended with a new route:

if (action === 'log_maintenance') {
  const persistence = new MaintenancePersistence();
  return persistence.handleMaintenanceLog(payload);
}

This leverages the existing GAS deployment endpoint rather than creating a new one, keeping infrastructure simple and reducing deployment surface area.

AWS Lambda Persistence Layer

The Lambda function stores task events in S3 and maintains a notification queue:

  • S3 Bucket: Maintenance tasks stored in queenofsandiego-maintenance-logs/ with timestamp-based prefixes
  • Event Structure: JSON files organized as YYYY/MM/DD/tasks_{hour}_{minute}.json for easy querying
  • Notification Queue: Separate prefix for pending digest notifications, cleaned daily

The Lambda is triggered synchronously from GAS with a 5-second timeout to ensure fast response, even if persistence fails.

Staging vs. Production Separation

Since maintenance.queenofsandiego.com serves both staging and production tasks, we implemented environment detection:

function isProduction(payload) {
  // Check if task source indicates production
  // or if user session is production-authenticated
  return payload.environment === 'production' || 
         getCurrentUser().isProdAuthorized();
}

For now, all test notifications route to jadasailing@gmail.com regardless of environment, preventing accidental alerts to the operations team during development.

Google Calendar Integration

Critical tasks automatically create calendar events in the "Jada Maintenance" calendar on jadasailing@gmail.com:

function createMaintenanceCalendarEvent(task) {
  const calendar = CalendarApp.getCalendarById('jada...@gmail.com');
  const event = calendar.createEvent(
    task.task_name,
    new Date(task.timestamp),
    new Date(new Date(task.timestamp).getTime() + 60 * 60000) // 1 hour duration
  );
  event.setTag('maintenance_system_id', task.id);
}

This ensures critical tasks appear in Sergio's calendar view alongside charter schedules and other operations events.

Deployment & CloudFront Configuration

The modified staging HTML was deployed to S3 and cached through CloudFront:

aws s3 cp staging-index.html s3://queenofsandiego-maintenance-staging/index.html
aws cloudfront create-invalidation \
  --distribution-id E... \
  --paths "/index.html" "/js/*"

CloudFront cache TTL is set to 60 seconds for the HTML file, allowing rapid iteration while still providing edge caching benefits.

GAS Deployment

The new MaintenancePersistence.gs` file was added to the project and deployed via clasp:

clasp push --force
clasp deploy --description "Add maintenance task notifications"

The --force flag was necessary because the file was created locally before being registered with the GAS project manifest.

Key Decisions & Rationale

  • Criticality-based notification timing: Research from incident response teams shows that digest-style notifications for routine tasks reduce alert fatigue by