```html

Building a Task Notification System for maintenance.queenofsandiego.com: Real-time Alerts and Persistence Layer

The maintenance tool at maintenance.queenofsandiego.com needed a critical capability: surfacing newly added tasks to the team and notifying stakeholders when work items appear. This post details the architecture, implementation, and infrastructure decisions made to solve this problem with production-grade reliability.

The Problem Statement

Crew members like Travis were adding maintenance tasks to the system, but there was no visibility mechanism for these additions. Without a notification system, tasks would sit unnoticed, and the team—particularly Sergio—had no way to know when action was required. Additionally, we needed to determine optimal notification cadence based on task criticality, balancing real-time alerts against notification fatigue.

Architecture Overview

We implemented a three-layer solution:

  • Persistence Layer: Lambda function to durably store task data and track changes
  • Notification Handler: Google Apps Script route to compose and send emails with appropriate urgency
  • Frontend Integration: Staging HTML modifications to trigger notifications when tasks are created

Technical Implementation Details

Lambda Persistence Function: MaintenancePersistence

Created /Users/cb/Documents/repos/sites/queenofsandiego.com/MaintenancePersistence.gs as a GAS-based Lambda handler. This function:

  • Accepts POST requests from the maintenance tool containing task payloads
  • Validates task structure (name, criticality level, assigned crew member)
  • Stores task state in DynamoDB with timestamp and creation metadata
  • Returns acknowledgment with task ID for client-side confirmation

The criticality field—LOW, MEDIUM, HIGH, CRITICAL—drives all downstream notification decisions. This allows us to apply backpressure to high-volume, low-priority tasks while ensuring critical safety issues reach stakeholders immediately.

// MaintenancePersistence.gs - Simplified structure
function handleTaskCreation(taskPayload) {
  const criticality = taskPayload.criticality || 'MEDIUM';
  const createdAt = new Date().toISOString();
  
  // Store with metadata for notification routing
  const persistenceRecord = {
    taskId: generateUUID(),
    title: taskPayload.title,
    description: taskPayload.description,
    criticality: criticality,
    assignedTo: taskPayload.assignedTo,
    createdAt: createdAt,
    createdBy: Session.getActiveUser().getEmail()
  };
  
  // Persist and trigger notifications
  persistToDatastore(persistenceRecord);
  routeNotification(persistenceRecord);
  
  return { success: true, taskId: persistenceRecord.taskId };
}

Notification Handler in BookingAutomation.gs

Modified /Users/cb/Documents/repos/sites/queenofsandiego.com/BookingAutomation.gs to add a new route handler for maintenance notifications. The handler implements industry-standard notification logic:

  • CRITICAL tasks: Sent immediately via email to Sergio and on-duty crew
  • HIGH tasks: Sent within 1 hour or when batch size reaches 3 items
  • MEDIUM/LOW tasks: Accumulated into daily digest emails sent at 18:00 UTC

This pattern mirrors incident severity models from SRE best practices, where page-worthy events trigger immediate notifications while lower-severity items batch for efficiency.

// In BookingAutomation.gs doPost handler
case 'log_maintenance_task':
  const taskData = request.maintenanceTask;
  const notificationResult = notifyMaintenanceStakeholders(taskData);
  return ContentService.createTextOutput(JSON.stringify(notificationResult));
  
function notifyMaintenanceStakeholders(task) {
  const recipients = getMaintenanceRecipients(task.criticality);
  const emailSubject = buildSubject(task);
  const emailBody = formatTaskNotification(task);
  
  if (task.criticality === 'CRITICAL') {
    MailApp.sendEmail({
      to: recipients.join(','),
      subject: emailSubject,
      htmlBody: emailBody,
      noReply: false
    });
  } else {
    // Batch for digest
    appendToNotificationQueue(task);
  }
  
  return { notified: recipients, method: task.criticality };
}

Staging HTML Modifications

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

  • JavaScript event listener on the task creation form submit handler
  • Payload construction that includes criticality level selected by crew members
  • POST request to the BookingAutomation endpoint with action type log_maintenance_task
  • User-facing toast notification confirming task submission and indicating notification status

The staging environment sends all notifications to jadasailing@gmail.com to allow testing without spamming the production stakeholder list.

Infrastructure Configuration

S3 Deployment

Staging HTML deployed to the S3 bucket backing the CloudFront distribution for maintenance.queenofsandiego.com:

aws s3 cp tools/maintenance/staging-index.html \
  s3://maintenance-tool-staging/index.html \
  --content-type "text/html" \
  --cache-control "no-cache"

CloudFront Cache Invalidation

After deploying staging changes, invalidated the CloudFront distribution cache to ensure users receive the updated HTML with notification handlers:

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

GAS Deployment

Deployed Google Apps Script changes using clasp:

clasp push
# Ensures MaintenancePersistence.gs and BookingAutomation.gs updates
# are deployed to the Apps Script project

Email Configuration

Set up calendar integration for jadasailing@gmail.com with a new calendar named "Jada Maintenance" to track task creation events. This allows correlation between calendar entries and actual work completion.

Key Design Decisions

Why use criticality-driven notification batching? Research from high-performing DevOps teams (cited in Google's SRE Book) shows that alert fatigue reduces response effectiveness. By batching low-priority tasks into daily digests, we preserve the team's ability to respond urgently to critical issues while still ensuring nothing falls through cracks.

Why store task state separately? While the maintenance HTML stores the source of truth, a durable persistence layer in Lambda provides audit trails, enables analytics, and allows notification logic to be independent of frontend changes.

Why separate staging from production in the same tool? Currently, both environments share infrastructure. Future separation could use environment variables in the GAS script or subdomain-based routing. For now, the staging environment's email recipient configuration isolates testing from production notifications.