Building a Real-Time Task Notification System for the JADA Maintenance Tool

The JADA maintenance tool at maintenance.queenofsandiego.com needed a critical capability: surface newly added tasks to the team and notify stakeholders in real-time. This post details the architecture, implementation, and infrastructure changes required to accomplish this across staging and production environments.

The Problem

The maintenance tool was functioning as a task dashboard, but lacked visibility into newly added tasks. When Travis added tasks via SMS integration, there was no mechanism to alert Sergio or other team members. Additionally, the team needed a scalable notification strategy that considered task criticality to determine notification frequency—alerting immediately for critical issues while batching lower-priority tasks into daily digests.

Architecture Overview

The solution involved four major components:

  • Frontend Enhancement: Modified staging HTML at /tools/maintenance/staging-index.html to detect new task additions
  • GAS Handler: New route in BookingAutomation.gs to handle log_maintenance POST requests
  • Persistence Layer: New file MaintenancePersistence.gs to manage task state and change detection
  • Notification Lambda: Serverless function to evaluate task criticality and dispatch emails via SES

Technical Implementation Details

Frontend Changes to staging-index.html

The live maintenance tool HTML at /tools/maintenance/index.html was cloned to staging. Key modifications included:

  • Added task change detection logic in the init() function
  • Implemented a task diff algorithm comparing current DOM state against a persisted baseline
  • On task additions, trigger a POST to GAS with payload: {action: 'log_maintenance', task: {...}, timestamp: Date.now()}
  • Maintained backward compatibility with existing access code authentication

The detection mechanism watches for new rows in the task table and serializes them before transmission:

// Pseudo-implementation detail
function detectNewTasks() {
  const currentTasks = Array.from(document.querySelectorAll('table.tasks tr'))
    .map(row => ({id: row.dataset.taskId, title: row.textContent}));
  
  const newTasks = currentTasks.filter(t => !previousState[t.id]);
  
  if (newTasks.length > 0) {
    notifyBackend(newTasks);
  }
  previousState = currentTasks;
}

GAS Router in BookingAutomation.gs

Extended the existing doPost handler to route maintenance requests:

// In BookingAutomation.gs doPost handler
if (params.action === 'log_maintenance') {
  const task = params.task;
  const timestamp = params.timestamp;
  MaintenancePersistence.recordTask(task, timestamp);
  MaintenancePersistence.evaluateAndNotify(task);
  return ContentService.createTextOutput(JSON.stringify({status: 'recorded'}));
}

This routing keeps all webhook handling in a single entry point, making debugging and monitoring straightforward.

MaintenancePersistence.gs - New Persistence Layer

Created a dedicated file to handle task state management and criticality evaluation. Key responsibilities:

  • Persist task metadata to Google Sheets (a dedicated sheet named "Maintenance Tasks")
  • Implement criticality classification logic based on task keywords and tags
  • Determine notification strategy: immediate vs. daily digest
  • Invoke Lambda function for email dispatch

Critical tasks (containing keywords like "electrical hazard," "safety," "emergency") trigger immediate SNS notifications. Standard tasks are batched for a daily 5 PM digest.

Lambda Notification Function

Deployed a Node.js Lambda function (deployed via CloudFormation) that:

  • Receives SNS events from GAS via HTTPS endpoint
  • Evaluates task criticality using configurable rules
  • Formats email with task details, assigned owner, and maintenance context
  • Dispatches via SES to stakeholders (initially jadasailing@gmail.com for staging)
  • Logs all notifications to CloudWatch for audit trails

The Lambda role requires permissions: ses:SendEmail, logs:CreateLogGroup, logs:CreateLogStream, logs:PutLogEvents.

Infrastructure and Deployment Strategy

Environment Separation

Since maintenance.queenofsandiego.com serves both staging and production, we implemented separation at multiple layers:

  • S3: Staging HTML deployed to s3://jada-website-assets/maintenance/staging-index.html
  • CloudFront: Query parameter routing: ?env=staging serves staging HTML; otherwise production
  • GAS: Script detects origin domain and routes accordingly
  • Email: Staging sends to jadasailing@gmail.com; production sends to ops team distribution list

CloudFront Configuration

The maintenance distribution (ID: E...) was updated with:

  • New origin behavior for /maintenance/staging-index.html
  • Cache invalidation pattern: /maintenance/staging-index.html* after each deployment
  • Query string forwarding enabled for environment detection

Invalidate cache post-deployment using:

aws cloudfront create-invalidation \
  --distribution-id E... \
  --paths "/maintenance/staging-index.html" "/maintenance/*"

Google Workspace Integration

Created a new calendar in the JADA Workspace: "Jada Maintenance" (shared with jadasailing@gmail.com). The MaintenanceCalendar.gs file syncs critical maintenance tasks to this calendar for visibility alongside charter schedules.

Key Architectural Decisions

Why Google Sheets for task persistence? The team already uses Google Workspace and authenticated with GAS scripts. No additional infrastructure required. Easy for non-technical team members to audit task history.

Why Lambda for notifications? Decouples email dispatch from GAS execution, which has stricter rate limits. Lambda scales automatically with notification volume and provides CloudWatch logging for operational visibility.

Why criticality-based notification strategy? Industry best practice (supported by research from high-performing ops teams like Netflix and Spotify) shows that "alert fatigue" reduces team responsiveness. Immediate alerts for critical issues, daily digests for standard tasks, balances urgency with cognitive load.

Why query parameter for staging vs. production? Avoids DNS duplication and allows testing production infrastructure behavior while isolating data. The single CloudFront distribution is easier to manage than parallel staging/prod CDNs.

Deployment and Testing Checklist