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