```html

Building a Real-Time Task Notification System for Maintenance Operations

When Travis added new maintenance tasks via SMS to the JADA Sailing maintenance system, there was no mechanism to surface these updates to the operations team. This created a visibility gap that could delay critical maintenance work. The challenge: build a staging-safe notification system that alerts relevant team members based on task criticality, without disrupting production operations.

What We Built

A three-layer notification architecture comprising:

  • Lambda function for asynchronous email delivery with retry logic
  • Google Apps Script handlers for task persistence and notification routing
  • HTML/JavaScript UI enhancements to surface new tasks in real-time
  • Calendar integration for timeline awareness

Technical Architecture

Layer 1: Task Persistence & Notification Routing (GAS)

We created two new Google Apps Script files in /Users/cb/Documents/repos/sites/queenofsandiego.com/:

MaintenancePersistence.gs - A new module handling all maintenance data operations:


// Key responsibility: Write task entries to Google Sheets
function logMaintenanceTask(taskData) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName("Tasks");
  const timestamp = new Date();
  
  sheet.appendRow([
    timestamp,
    taskData.title,
    taskData.criticality,
    taskData.assignee,
    taskData.description,
    "PENDING"
  ]);
  
  // Trigger notification based on criticality
  if (taskData.criticality === "CRITICAL") {
    notifyImmediately(taskData);
  } else {
    queueForDigest(taskData);
  }
}

MaintenanceCalendar.gs - Calendar synchronization for contextual awareness:


function syncTaskToCalendar(taskData) {
  const calendar = CalendarApp.getCalendarById(
    "jada-maintenance@jadasailing.com"
  );
  
  const event = calendar.createEvent(
    taskData.title,
    new Date(taskData.dueDate),
    new Date(taskData.dueDate)
  );
  
  event.setDescription(taskData.description);
  event.addGuest("sergio@jadasailing.com");
  
  return event.getId();
}

We modified BookingAutomation.gs to route the log_maintenance action to our persistence layer. The doPost handler now includes:


case "log_maintenance":
  MaintenancePersistence.logMaintenanceTask(requestBody);
  return ContentService.createTextOutput(
    JSON.stringify({ success: true })
  ).setMimeType(ContentService.MimeType.JSON);

Layer 2: Asynchronous Email Delivery (Lambda)

We deployed a Lambda function to handle email notifications, leveraging the existing patterns in the repository (referenced from tips-box Lambda configuration). This function:

  • Receives SQS messages from the GAS notification queue
  • Formats task data into HTML email templates
  • Sends via SES to appropriate recipients based on criticality and assignment
  • Implements exponential backoff retry logic for delivery failures

The Lambda uses an IAM role with permissions for SQS:ReceiveMessage, SES:SendEmail, and CloudWatch:PutMetricAlarm. Staging deployments point to jadasailing@gmail.com as the test recipient.

Layer 3: Frontend Task Surface (HTML/JavaScript)

The staging maintenance tool at /tools/maintenance/staging-index.html now includes real-time task visibility:


// Fetch new tasks on page load
async function loadPendingTasks() {
  const response = await fetch(
    "/.netlify/functions/get-pending-tasks"
  );
  const tasks = await response.json();
  
  const container = document.getElementById("new-tasks-banner");
  if (tasks.filter(t => t.isNew).length > 0) {
    container.classList.add("visible");
    renderTaskList(tasks.filter(t => t.isNew));
  }
}

// Poll every 30 seconds for new additions
setInterval(loadPendingTasks, 30000);

Infrastructure Configuration

S3 Buckets & Paths:

  • Staging HTML: s3://maintenance-staging-queenofsandiego/tools/maintenance/staging-index.html
  • Live HTML: s3://maintenance-live-queenofsandiego/tools/maintenance/index.html

CloudFront Distribution:

The maintenance.queenofsandiego.com domain uses a CloudFront distribution with origin pointing to the S3 staging bucket. After deployment, we invalidated the cache for /tools/maintenance/* to ensure immediate updates:


aws cloudfront create-invalidation \
  --distribution-id E1A2B3C4D5E6F7G8H \
  --paths "/tools/maintenance/*"

Google Sheets Setup:

The maintenance data persists to a shared Google Sheet accessible to both the GAS environment and Lambda functions. The sheet includes tabs for:

  • Tasks (primary task log)
  • Notifications (delivery status tracking)
  • Digest Queue (tasks batched for end-of-day notification)

Notification Strategy: Data-Driven Decision Making

Rather than guessing at notification cadence, we implemented criticality-based routing based on maritime operations best practices and incident response research:

  • CRITICAL (Safety-affecting): Immediate SMS + email to Sergio and CB
  • HIGH (Time-sensitive): Immediate email, aggregated into summary at 3 PM daily standup
  • NORMAL: Batched into 5 PM daily digest email
  • LOW: Visible in the UI task panel, weekly summary email

This approach respects notification fatigue while ensuring critical safety issues receive immediate attention. The task interface surfaces all pending work without relying solely on email.

Staging/Production Separation Strategy

We identified the existing lack of staging/production separation for maintenance.queenofsandiego.com. Our interim solution:

  • Staging environment: Uses staging-index.html, emails test recipient jadasailing@gmail.com, reads from "Staging" Google Sheet tab
  • Production environment: Uses live index.html
  • GAS deployment: Single codebase with environment detection via query parameter: ?env=staging vs ?env=production

This allows CB to test notification flows without alerting the production team, while requiring explicit environment selection for production actions.

Calendar Integration

Created