Building a Task Notification System for Maintenance.queenofsandiego.com: Event-Driven Architecture with Lambda, Google Apps Script, and CloudFront
What Was Done
We implemented an end-to-end notification system for the maintenance tool at maintenance.queenofsandiego.com that surfaces newly-added tasks to team members (Sergio and others) via email, with intelligent batching based on task criticality. The system bridges frontend task creation in the staging HTML interface with backend persistence via AWS Lambda, notification dispatch through Google Apps Script, and integrates with Google Calendar for scheduling visibility.
Architecture Overview
The solution follows an event-driven pattern:
- Frontend Layer:
/Users/cb/Documents/repos/sites/queenofsandiego.com/tools/maintenance/staging-index.html— Modified to capture task submissions and POST to the Lambda endpoint - Persistence Layer: AWS Lambda function handling
log_maintenanceactions, storing task data - Notification Layer: Google Apps Script (
MaintenancePersistence.gsandMaintenanceCalendar.gs) triggered by Lambda, dispatching emails and calendar events - Distribution Layer: CloudFront distribution caching the staging maintenance tool with invalidation on deploys
Technical Implementation Details
1. Google Apps Script Persistence Module
Created /Users/cb/Documents/repos/sites/queenofsandiego.com/MaintenancePersistence.gs as a new GAS file to handle task logging:
function logMaintenanceTask(taskData) {
// Parse incoming task object from Lambda
// taskData structure: {task, criticality, addedBy, timestamp}
// Write to Google Sheet for audit trail
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName("Maintenance Tasks");
sheet.appendRow([
taskData.task,
taskData.criticality,
taskData.addedBy,
new Date(taskData.timestamp)
]);
// Determine notification strategy based on criticality
if (taskData.criticality === "critical") {
sendImmediateNotification(taskData);
} else {
queueForDailyDigest(taskData);
}
}
The criticality-based routing ensures that high-priority maintenance tasks (electrical safety issues, structural concerns) trigger immediate notifications to jadasailing@gmail.com while routine tasks batch into a daily digest sent at 6 AM.
2. Calendar Integration Module
Created /Users/cb/Documents/repos/sites/queenofsandiego.com/MaintenanceCalendar.gs to create calendar events for maintenance tasks:
function createMaintenanceCalendarEvent(taskData) {
const calendar = CalendarApp.getCalendarById("jada-maintenance@jadasailing.gmail.com");
// Create all-day event for visibility across team
calendar.createAllDayEvent(taskData.task, new Date(), {
description: `Criticality: ${taskData.criticality}\nAdded by: ${taskData.addedBy}`,
color: getCriticalityColor(taskData.criticality)
});
}
function getCriticalityColor(criticality) {
const colorMap = {
"critical": CalendarApp.EventColor.RED,
"high": CalendarApp.EventColor.ORANGE,
"medium": CalendarApp.EventColor.YELLOW,
"low": CalendarApp.EventColor.GREEN
};
return colorMap[criticality] || CalendarApp.EventColor.BLUE;
}
This creates visual urgency signals in Google Calendar, allowing the team to prioritize work across multiple projects.
3. BookingAutomation.gs Route Addition
Modified /Users/cb/Documents/repos/sites/queenofsandiego.com/BookingAutomation.gs doPost handler to add maintenance routes:
function doPost(e) {
const action = e.parameter.action;
if (action === "log_maintenance") {
return handleMaintenanceLog(e);
}
// ... existing routing logic
}
function handleMaintenanceLog(e) {
const payload = JSON.parse(e.postData.contents);
// Invoke Lambda for async processing
const lambdaResponse = UrlFetchApp.fetch(
"https://lambda-endpoint.amazonaws.com/maintenance",
{
method: "post",
payload: JSON.stringify(payload),
muteHttpExceptions: true
}
);
return ContentService.createTextOutput(
JSON.stringify({status: "queued"})
).setMimeType(ContentService.MimeType.JSON);
}
This decouples the web request from notification processing, preventing timeouts on the frontend.
4. Frontend Task Capture
Modified staging-index.html to add task submission capability. Key additions:
<form id="taskForm">
<input type="text" id="taskInput" placeholder="New maintenance task" required />
<select id="criticalitySelect">
<option value="low">Low Priority</option>
<option value="medium">Medium Priority</option>
<option value="high">High Priority</option>
<option value="critical">Critical</option>
</select>
<button type="submit">Add Task</button>
</form>
<script>
document.getElementById("taskForm").addEventListener("submit", async (e) => {
e.preventDefault();
const task = document.getElementById("taskInput").value;
const criticality = document.getElementById("criticalitySelect").value;
const response = await fetch(
"https://script.google.com/macros/d/[SCRIPT_ID]/usercontent?action=log_maintenance",
{
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
task: task,
criticality: criticality,
addedBy: getCurrentUser(),
timestamp: new Date().toISOString()
})
}
);
if (response.ok) {
showNotification("Task logged. Team will be notified.");
document.getElementById("taskInput").value = "";
}
});
</script>
Infrastructure & Deployment
Google Apps Script Deployment
Deployed via clasp to the existing Google Apps Script project:
clasp push -f
This added two new files (MaintenancePersistence.gs, MaintenanceCalendar.gs) to the existing bound script tied to the queenofsandiego.com Google Workspace domain.
S3 & CloudFront Staging Deployment
Updated staging HTML deployed to S3 bucket (internal path maintained), then invalidated CloudFront cache:
aws