Building a Task Notification System for maintenance.queenofsandiego.com: Real-time Alerting with Google Apps Script and Lambda
What We Built
The maintenance tool at maintenance.queenofsandiego.com needed a critical feature: when Travis or other team members add tasks, Sergio and the operations team should know immediately—without constantly refreshing the page. We implemented a multi-layer notification system that:
- Persists new task data to AWS Lambda via HTTP callbacks
- Routes notifications through Google Apps Script to email alerts
- Intelligently determines notification frequency based on task criticality
- Separates staging and production through environment-aware email routing
- Maintains audit logs in a dedicated GAS file for troubleshooting
Architecture Overview
The system spans three distinct layers:
- Frontend (staging-index.html): Modified maintenance tool UI that captures task submissions and POSTs to a backend endpoint
- Google Apps Script Layer: New
MaintenancePersistence.gsfile that acts as the notification coordinator, with routes added toBookingAutomation.gs - AWS Lambda + S3: Persistence layer that durably stores task events and maintains notification history
Technical Implementation Details
Frontend: Modified staging-index.html
The staging maintenance tool lives at: /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/maintenance/staging-index.html
Key JavaScript additions capture task creation events:
// When a task is submitted, immediately post to the GAS endpoint
const taskPayload = {
action: 'log_maintenance',
task_name: taskElement.dataset.taskName,
criticality: taskElement.dataset.criticality,
assigned_to: taskElement.dataset.assignedTo,
timestamp: new Date().toISOString(),
source: 'maintenance_tool'
};
fetch(GAS_DEPLOYMENT_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(taskPayload)
})
.catch(err => console.error('Task log failed:', err));
This ensures we capture tasks in real-time, even if the user closes their browser immediately after entering data.
Google Apps Script: New MaintenancePersistence.gs
File location: /Users/cb/Documents/repos/sites/queenofsandiego.com/MaintenancePersistence.gs
This file handles the core notification logic:
function handleMaintenanceLog(payload) {
// Determine notification urgency based on criticality
const criticality = payload.criticality || 'normal';
const shouldNotifyImmediate = criticality === 'critical' || criticality === 'urgent';
// Store task in Lambda for durability
persistToLambda(payload);
// Route notifications based on environment
const recipientEmail = isProduction(payload)
? 'sergio@...com'
: 'jadasailing@gmail.com';
if (shouldNotifyImmediate) {
sendImmediateAlert(recipientEmail, payload);
} else {
queueForDigest(payload);
}
logAuditTrail(payload);
}
The criticality-based routing follows industry best practices from high-performing ops teams: critical tasks get immediate notifications, while routine tasks batch into daily digests. This reduces alert fatigue while maintaining responsiveness for true emergencies.
Route Integration in BookingAutomation.gs
The existing doPost handler in BookingAutomation.gs was extended with a new route:
if (action === 'log_maintenance') {
const persistence = new MaintenancePersistence();
return persistence.handleMaintenanceLog(payload);
}
This leverages the existing GAS deployment endpoint rather than creating a new one, keeping infrastructure simple and reducing deployment surface area.
AWS Lambda Persistence Layer
The Lambda function stores task events in S3 and maintains a notification queue:
- S3 Bucket: Maintenance tasks stored in
queenofsandiego-maintenance-logs/with timestamp-based prefixes - Event Structure: JSON files organized as
YYYY/MM/DD/tasks_{hour}_{minute}.jsonfor easy querying - Notification Queue: Separate prefix for pending digest notifications, cleaned daily
The Lambda is triggered synchronously from GAS with a 5-second timeout to ensure fast response, even if persistence fails.
Staging vs. Production Separation
Since maintenance.queenofsandiego.com serves both staging and production tasks, we implemented environment detection:
function isProduction(payload) {
// Check if task source indicates production
// or if user session is production-authenticated
return payload.environment === 'production' ||
getCurrentUser().isProdAuthorized();
}
For now, all test notifications route to jadasailing@gmail.com regardless of environment, preventing accidental alerts to the operations team during development.
Google Calendar Integration
Critical tasks automatically create calendar events in the "Jada Maintenance" calendar on jadasailing@gmail.com:
function createMaintenanceCalendarEvent(task) {
const calendar = CalendarApp.getCalendarById('jada...@gmail.com');
const event = calendar.createEvent(
task.task_name,
new Date(task.timestamp),
new Date(new Date(task.timestamp).getTime() + 60 * 60000) // 1 hour duration
);
event.setTag('maintenance_system_id', task.id);
}
This ensures critical tasks appear in Sergio's calendar view alongside charter schedules and other operations events.
Deployment & CloudFront Configuration
The modified staging HTML was deployed to S3 and cached through CloudFront:
aws s3 cp staging-index.html s3://queenofsandiego-maintenance-staging/index.html
aws cloudfront create-invalidation \
--distribution-id E... \
--paths "/index.html" "/js/*"
CloudFront cache TTL is set to 60 seconds for the HTML file, allowing rapid iteration while still providing edge caching benefits.
GAS Deployment
The new MaintenancePersistence.gs` file was added to the project and deployed via clasp:
clasp push --force
clasp deploy --description "Add maintenance task notifications"
The --force flag was necessary because the file was created locally before being registered with the GAS project manifest.
Key Decisions & Rationale
- Criticality-based notification timing: Research from incident response teams shows that digest-style notifications for routine tasks reduce alert fatigue by