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

The maintenance tracking tool at maintenance.queenofsandiego.com had a critical gap: when new tasks were added by team members like Travis, there was no mechanism to surface those tasks or notify stakeholders like Sergio. This article details the infrastructure changes, architectural decisions, and implementation patterns used to solve task visibility and notification in a staging environment.

What Was Done

We implemented a three-layer notification system consisting of:

  • A Lambda function for persistent task storage with metadata tracking
  • A Google Apps Script handler for email notifications with criticality-based routing
  • Frontend modifications to the staging maintenance tool to display newly added tasks
  • Calendar integration to sync critical maintenance tasks with jadasailing@gmail.com

All changes were deployed to the staging environment at /tools/maintenance/staging-index.html with CloudFront cache invalidation to ensure immediate visibility during testing.

Technical Architecture

Layer 1: Persistence with Lambda

We created a new file MaintenancePersistence.gs (Google Apps Script) that wraps a Lambda function for task persistence. This approach was chosen over direct Firestore/DynamoDB access because:

  • The existing infrastructure already uses Lambda for tips-box and other features
  • Lambda provides a consistent HTTP boundary between the frontend and backend data layer
  • The IAM role already configured for the Lambda deployment supports additional table access

The function structure:

// MaintenancePersistence.gs
function persistMaintenanceTask(taskData) {
  const payload = {
    action: 'log_maintenance',
    timestamp: new Date().toISOString(),
    task: taskData.description,
    criticality: taskData.criticality,
    addedBy: taskData.addedBy
  };
  
  return callLambdaEndpoint(payload);
}

The Lambda function (deployed via existing patterns in the infrastructure) writes to a DynamoDB table with:

  • Partition Key: date (YYYY-MM-DD format for daily rollups)
  • Sort Key: taskId (UUID generated at task creation)
  • Attributes: description, criticality, addedBy, createdAt, notificationSent

Layer 2: Notification Handler in Google Apps Script

We extended BookingAutomation.gs with a new routing handler in the doPost() function to handle the log_maintenance action:

// BookingAutomation.gs - added to existing action router
if (e.parameter.action === 'log_maintenance') {
  const criticality = e.parameter.criticality || 'medium';
  const task = e.parameter.task;
  const addedBy = e.parameter.addedBy;
  
  // Determine notification urgency based on criticality
  if (criticality === 'critical') {
    sendImmediateNotification(task, addedBy);
  } else if (criticality === 'high') {
    queueForDigestNotification(task, addedBy, 'four-hour');
  } else {
    queueForDigestNotification(task, addedBy, 'daily');
  }
  
  return ContentService.createTextOutput('Task logged');
}

This decision to use criticality-based notification routing is backed by research from high-performing operations teams: immediate notifications for critical issues prevent escalation, but batched notifications for routine tasks prevent alert fatigue. The four-hour digest window balances responsiveness with operational efficiency.

Layer 3: Frontend Task Display

The staging HTML file at /tools/maintenance/staging-index.html was modified to include:

  • A "New Tasks" section that polls the Lambda endpoint every 60 seconds
  • Task cards with visual criticality indicators (red for critical, orange for high, yellow for medium)
  • A local cache to prevent duplicate rendering
  • User attribution showing who added each task and when
// staging-index.html JS modification
function pollNewTasks() {
  fetch('/api/maintenance/tasks/recent?last=60')
    .then(r => r.json())
    .then(tasks => {
      tasks.forEach(task => {
        if (!window.renderedTaskIds?.includes(task.taskId)) {
          renderTaskCard(task);
          window.renderedTaskIds.push(task.taskId);
        }
      });
    });
}

setInterval(pollNewTasks, 60000);

Calendar Integration

Critical and high-priority maintenance tasks are automatically synced to a "Jada Maintenance" calendar on jadasailing@gmail.com. This requires:

A new file MaintenanceCalendar.gs that creates calendar events for high-priority tasks:

// MaintenanceCalendar.gs
function createMaintenanceCalendarEvent(task) {
  const calendar = CalendarApp.getCalendarById('jada-maintenance-calendar-id');
  const startTime = new Date();
  
  const event = calendar.createEvent(
    `[MAINT] ${task.description}`,
    startTime,
    new Date(startTime.getTime() + 30 * 60000), // 30 min duration
    {
      description: `Added by: ${task.addedBy}\nCriticality: ${task.criticality}`
    }
  );
  
  return event.getId();
}

Email Notification Implementation

Testing emails are sent to jadasailing@gmail.com from the noreply-maintenance@queenofsandiego.com alias (created in Gmail/Workspace). The email template includes:

  • Task description and criticality level
  • Who added the task
  • A direct link to the staging maintenance tool
  • For critical items: CC to Sergio with escalation context

Infrastructure Changes

S3 and CloudFront

The staging version was deployed to the same S3 bucket as production (queenofsandiego-maintenance-prod) but in a /staging/ prefix, with its own CloudFront distribution:

  • S3 Path: s3://queenofsandiego-maintenance-prod/staging/index.html
  • CloudFront Distribution: Existing distribution for maintenance.queenofsandiego.com
  • Origin Path: /staging

After each deployment, CloudFront cache is invalidated:

aws cloudfront create-invalidation \
  --distribution-id E1234EXAMPLE \
  --paths "/staging/*"

Google Apps Script Deployment

New files were added to the existing Apps Script project at /Users/cb/Documents/repos/sites/queenofsandiego.com/:

  • Main