Building a Real-Time Maintenance Task Notification System for Queen of San Diego

Managing vessel maintenance across a charter operation requires coordinated effort between crew members, with timely visibility into newly assigned tasks. This article documents the architecture and implementation of a real-time notification system for the maintenance tool at maintenance.queenofsandiego.com, designed to surface new maintenance tasks and alert team members via email based on task criticality.

Problem Statement

The maintenance tool was deployed but lacked a mechanism to notify crew members when new tasks were added. Travis could add tasks via the tool, but without push notifications or email alerts, team members like Sergio wouldn't know new work was queued. This created a visibility gap that could delay critical maintenance or leave tasks unaddressed.

Additionally, the team needed to establish a sustainable notification cadence: should every new task trigger an immediate alert, or should notifications be batched and sent periodically based on task severity? Industry best practices from high-performing operations suggest a tiered approach where critical tasks trigger immediate notifications while routine tasks accumulate in digests.

Architecture Overview

The solution consists of four integrated components:

  • Persistence Layer: MaintenancePersistence.gs — A new Google Apps Script file that manages task state and detects newly added items
  • Notification Handler: Lambda function deployed to AWS that evaluates task criticality and dispatches emails asynchronously
  • GAS Router: Modified BookingAutomation.gs to route maintenance-related API calls from the frontend HTML
  • Frontend: Updated staging HTML at /tools/maintenance/staging-index.html with task submission handlers

Technical Implementation

Persistence Layer: MaintenancePersistence.gs

Created a new GAS file to maintain task history and detect deltas. The file manages a simple state document stored in Google Drive with this structure:

{
  "lastCheckedTimestamp": 1715000000,
  "taskHashes": {
    "task_id_1": "content_hash",
    "task_id_2": "content_hash"
  },
  "pendingNotifications": [
    {
      "taskId": "task_id_1",
      "criticality": "critical",
      "addedAt": 1715000000,
      "notifiedAt": null
    }
  ]
}

This allows the system to detect when new tasks appear without storing redundant task data. The hash-based approach ensures we don't re-notify on task updates.

GAS Integration: BookingAutomation.gs Routes

Extended the existing BookingAutomation.gs doPost handler to recognize a new action type: log_maintenance. The router now includes:

if (action === "log_maintenance") {
  const taskData = {
    title: e.parameter.title,
    description: e.parameter.description,
    criticality: e.parameter.criticality || "routine",
    assignedTo: e.parameter.assignedTo,
    dueDate: e.parameter.dueDate,
    addedBy: e.parameter.addedBy,
    timestamp: new Date().getTime()
  };
  
  const result = MaintenancePersistence.recordTask(taskData);
  
  if (result.isNew) {
    // Invoke Lambda for async notification
    UrlFetchApp.fetch(LAMBDA_ENDPOINT, {
      method: "post",
      payload: JSON.stringify({
        taskId: result.taskId,
        criticality: taskData.criticality
      })
    });
  }
  
  return ContentService.createTextOutput(JSON.stringify(result));
}

This design keeps GAS lightweight by delegating notification dispatch to Lambda, which can handle exponential backoff and retry logic without blocking the form submission.

Lambda Notification Function

Deployed a Lambda function (naming pattern: jada-maintenance-notifier) with Node.js 18 runtime, configured with an IAM role granting:

  • ses:SendEmail permission to AWS SES for email dispatch
  • logs:CreateLogGroup, logs:CreateLogStream, logs:PutLogEvents for CloudWatch logging

The function implements a criticality-based notification strategy:

  • Critical (P0): Immediate email to both Sergio and maintenance lead
  • High (P1): Immediate email, batched in 15-minute digest
  • Routine (P2): Accumulated in daily digest at 18:00 UTC

This approach follows the Eisenhower Matrix principle: urgent + important tasks get immediate attention, while important but non-urgent tasks batch up for efficient processing.

Email Configuration

For the staging environment, notifications route to jadasailing@gmail.com with sender address from an AWS SES verified domain (pending verification of the appropriate alias). In production, the system will route to individual crew email addresses stored in the task assignment field.

A Google Calendar named "Jada Maintenance" was created under jadasailing@gmail.com to provide an additional visibility layer. The Lambda function posts task summaries as calendar events with:

  • Event title: Task name and criticality level
  • Event time: Due date from task, or today if unspecified
  • Event description: Full task description and assignee
  • Event color: Red for critical, orange for high, blue for routine

This creates a searchable, time-aware audit trail of maintenance work.

Infrastructure and Deployment

S3 and CloudFront

The staging maintenance tool is deployed to S3 bucket jada-maintenance-staging at path /tools/maintenance/staging-index.html. CloudFront distribution (ID to be specified in deployment docs) caches this content with a 5-minute TTL to balance freshness with performance.

After each deployment, the CloudFront cache is invalidated with:

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

GAS Deployment

The MaintenancePersistence.gs file was added to the Apps Script project and pushed via clasp:

clasp push

The script ID matches the production deployment, ensuring development and staging use the same Google Apps Script backend. Version control is maintained via the GAS editor's deployment history.

Key Design Decisions

Why Lambda over Google Apps Script for notifications? GAS has strict execution time limits (6 minutes) and quota constraints on email sending. Lambda provides unlimited execution duration, native AWS SES integration, and built-in retry logic via dead-letter queues.

Why hash-based delta detection? Polling the full task list every minute would be expensive and error-prone. Computing content hashes allows us to detect true additions without maintaining complex state machines or timestamps that could drift across systems.

Why both email and calendar notifications? Email reaches crew immediately but can be lost in inbox noise. Calendar events provide a persistent, queryable record that integrates with existing scheduling tools. Together, they create redundancy and