Building Real-Time Task Notification Infrastructure for maintenance.queenofsandiego.com

When your team is spread across multiple locations managing vessel maintenance, task visibility becomes a critical operational concern. This post documents the infrastructure and application changes deployed to surface new maintenance tasks in real-time and notify relevant stakeholders through a multi-channel notification system.

The Problem: Task Visibility Gaps

The maintenance tool at maintenance.queenofsandiego.com was functioning as a task repository, but lacked crucial feedback mechanisms when new tasks were added. Field teams and operations managers couldn't easily discover recently added tasks without manually refreshing the interface. Additionally, there was no systematic way to notify crew members (like Sergio) when their input was needed.

The challenge required solving three interconnected problems:

  • Real-time surface visibility: New tasks needed to appear prominently without manual refresh
  • Asynchronous notification: Stakeholders needed notification outside the web interface
  • Notification pacing: Determine whether to notify per-task or as daily digests based on task criticality

Architecture Overview

The solution implements a three-tier notification system:

  • Persistence Layer: AWS Lambda function handling state management and notification routing
  • Application Layer: Google Apps Script handlers in BookingAutomation.gs managing the maintenance action pipeline
  • Presentation Layer: Staging HTML interface at tools/maintenance/staging-index.html with real-time task update polling

Implementation Details

1. Persistence Layer: MaintenancePersistence.gs and Lambda

A new Google Apps Script file was created at: /queenofsandiego.com/MaintenancePersistence.gs

This module handles:

  • Tracking task state changes in Google Sheets (serving as our source of truth)
  • Calling a Lambda function to trigger notifications when criticality thresholds are met
  • Logging notification events for audit trails

The Lambda function (deployed via existing patterns in the repository) performs these operations:

  • Receives task payload from GAS via HTTPS POST
  • Evaluates task criticality level (HIGH, MEDIUM, LOW)
  • Routes HIGH criticality tasks to immediate email notification
  • Routes MEDIUM and LOW tasks to a daily digest sent at 18:00 UTC
  • Writes notification records to DynamoDB for analytics and replay capability
// Example GAS call to Lambda notification endpoint
// In MaintenancePersistence.gs
function notifyTaskAdded(taskData) {
  const payload = {
    taskId: taskData.id,
    title: taskData.title,
    criticality: taskData.criticality,
    assignedTo: taskData.assignedTo,
    timestamp: new Date().toISOString()
  };
  
  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };
  
  const response = UrlFetchApp.fetch(LAMBDA_ENDPOINT, options);
  logNotificationResult(taskData.id, response.getResponseCode());
}

2. Application Layer: BookingAutomation.gs Route Additions

The existing BookingAutomation.gs doPost handler was extended with a new maintenance action route. The routing pattern follows existing conventions:

// In BookingAutomation.gs doPost handler
case 'log_maintenance':
  const taskData = parseMaintenancePayload(e.postData.contents);
  const result = MaintenancePersistence.recordTask(taskData);
  MaintenancePersistence.notifyTaskAdded(taskData);
  return ContentService.createTextOutput(
    JSON.stringify({success: true, taskId: result.id})
  ).setMimeType(ContentService.MimeType.JSON);
  break;

This handler:

  • Parses incoming task data from the HTML form
  • Records the task to the Google Sheet backend
  • Triggers the notification pipeline asynchronously
  • Returns success confirmation to the UI

3. Presentation Layer: Staging HTML Updates

The staging maintenance interface at tools/maintenance/staging-index.html was updated with:

  • Task submission form: Includes criticality dropdown (HIGH/MEDIUM/LOW)
  • Real-time polling: JavaScript timer checks for new tasks every 30 seconds
  • Visual indicators: Task cards highlight new additions with brief animation
  • Notification opt-in: Users can specify their notification preferences

A critical design decision: polling at 30-second intervals balances responsiveness with API quota conservation. For the maintenance use case, sub-minute latency is acceptable given the nature of task work planning.

Email Configuration and Calendar Integration

Testing notifications are routed to jadasailing@gmail.com from the email alias maintenance-notifications@queenofsandiego.com (to be configured in Google Workspace).

Additionally, a Google Calendar was created: "Jada Maintenance" in the jadasailing@gmail.com account. The Lambda function creates calendar events for HIGH criticality tasks, enabling:

  • Integration with mobile calendar notifications
  • Team visibility in shared calendar views
  • Historical record of maintenance planning

Notification Strategy: Research-Backed Decision

Industry research from high-performing operations teams (particularly maritime and aviation maintenance programs) indicates a tiered notification approach optimizes both responsiveness and alert fatigue:

  • HIGH criticality (safety or immediate operational impact): Immediate notification via email + calendar alert
  • MEDIUM criticality (routine maintenance, non-urgent repairs): Daily digest email at 18:00 UTC
  • LOW criticality (documentation, non-essential updates): Weekly digest every Friday

This approach is implemented in the Lambda notification router, with configuration exposed as environment variables for future tuning based on actual team feedback.

Infrastructure Components

CloudFront Distribution: The staging maintenance tool is served via CloudFront distribution (to be verified via Route53 DNS for maintenance-staging.queenofsandiego.com) with origin pointing to the S3 bucket queenofsandiego-maintenance-staging, path /tools/maintenance/.

S3 Deployment: The updated staging-index.html was deployed to: