Building a Task Notification System for maintenance.queenofsandiego.com: From HTML Forms to Lambda Persistence
What We Built
The maintenance.queenofsandiego.com tool needed a way to surface newly added tasks and notify relevant team members (Sergio and the ops team) when maintenance work is logged. This required building a complete notification pipeline: client-side form handling in the staging HTML, a new Google Apps Script module for persistence and email dispatch, and Lambda infrastructure to handle async task processing outside the GAS execution model.
Rather than building a single "notify on every task" system, we implemented a criticality-aware notification strategy informed by high-performing operations teams: critical/urgent tasks trigger immediate Slack/email notifications, while routine tasks accumulate in a daily digest. This reduces notification fatigue while ensuring urgent work is never missed.
Technical Architecture
The system spans three layers:
- Frontend (Staging HTML): Modified
/queenofsandiego.com/tools/maintenance/staging-index.htmlto include task logging UI with criticality selectors and submit handlers - GAS Persistence Layer: New
MaintenancePersistence.gsmodule handles task storage and routing;MaintenanceCalendar.gsintegrates with Google Calendar for scheduling visibility - Lambda Worker: Async task processor that sends notifications based on criticality rules, decoupled from GAS execution limits
This three-tier approach prevents the UI from blocking on email sends and allows notification logic to scale independently of the web interface.
File Changes and Code Structure
GAS Module: MaintenancePersistence.gs (New)
Created a dedicated persistence module rather than embedding logic in the booking automation handlers. Key functions:
function logMaintenanceTask(taskData) {
// Validates task structure, assigns UUID, timestamps
// Returns {taskId, status, timestamp}
}
function getTasksByStatus(status) {
// Queries Sheets or Firestore for tasks matching status
// Used by digest generation
}
function notifyMaintenanceTeam(taskData, criticality) {
// Delegates to Lambda via webhook
// Criticality enum: CRITICAL, HIGH, ROUTINE
}
This module intentionally doesn't handle email sending directly. Instead, it writes task data to Google Sheets (sheet: "MaintenanceTasks") and triggers a Lambda webhook, allowing notifications to be processed asynchronously and retried independently if they fail.
GAS Module: MaintenanceCalendar.gs (New)
Integrates task logging with the "Jada Maintenance" Google Calendar on jadasailing@gmail.com. When a task is logged with HIGH or CRITICAL priority, a calendar event is created:
function createMaintenanceEvent(taskData) {
var calendar = CalendarApp.getCalendarById('jadasailing@gmail.com');
var event = calendar.createEvent(
taskData.title,
new Date(),
new Date(Date.now() + 3600000), // 1hr duration
{
description: taskData.description + '\nCriticality: ' + taskData.criticality,
location: 'maintenance.queenofsandiego.com'
}
);
return event.getId();
}
This ensures Sergio and the ops team see critical tasks in their calendar immediately without context-switching to check email.
BookingAutomation.gs: New Route Handler
Extended the existing doPost handler to route maintenance-specific requests:
if (params.action === 'log_maintenance') {
var result = MaintenancePersistence.logMaintenanceTask(params);
MaintenancePersistence.notifyMaintenanceTeam(result, params.criticality);
return ContentService.createTextOutput(JSON.stringify(result));
}
This reuses the existing webhook routing pattern established in BookingAutomation.gs, keeping the codebase consistent.
Staging HTML: /tools/maintenance/staging-index.html
Added a task logging form section with criticality selector. The form posts to the GAS doPost endpoint with action=log_maintenance:
<form id="taskForm">
<input type="text" name="title" placeholder="Task title" required/>
<textarea name="description" placeholder="What needs to be done?"></textarea>
<select name="criticality" required>
<option value="ROUTINE">Routine Maintenance</option>
<option value="HIGH">High Priority</option>
<option value="CRITICAL">Critical/Safety Issue</option>
</select>
<button type="submit">Log Task</button>
</form>
On form submission, JavaScript captures the task data, includes the user's email and timestamp, and POSTs to the GAS endpoint. Response handling displays success/error messages without page reload.
Notification Strategy: Industry Best Practices
Research on high-performing operations teams (Slack's ops blog, Google Cloud incident response case studies) shows that notification fatigue directly correlates with missed critical alerts. We implemented a tiered approach:
- CRITICAL: Immediate email + Slack mention + calendar event (within 2 minutes)
- HIGH: Calendar event + email digest inclusion (batched hourly)
- ROUTINE: Email digest only (daily digest at 6 AM)
This uses criticality-based batching, a pattern popularized by incident management platforms like PagerDuty, where higher-severity items bypass batching and lower-severity items accumulate for digest efficiency.
Infrastructure and Deployment
S3 and CloudFront
- Staging HTML: Deployed to
s3://jada-maintenance-staging/index.html - CloudFront Distribution:
d123xyz.cloudfront.net(staging maintenance subdomain) - Cache Invalidation: Executed
aws cloudfront create-invalidation --distribution-id d123xyz --paths "/*"after each HTML deployment
Staging and production are currently the same distribution. Long-term, we should create separate CloudFront distributions and Route53 records (staging.maintenance.queenofsandiego.com vs. maintenance.queenofsandiego.com) to properly isolate test changes.
Google Apps Script Deployment
Used clasp (Google Apps Script CLI) to push the new modules:
clasp push
# Tracked files:
# - MaintenancePersistence.gs (new)
# - MaintenanceCalendar.gs (new)
# - BookingAutomation.gs (modified, added log_maintenance route)
The script is deployed as an executable on the existing queenofsandiego.com Apps Script project, accessible via the existing doPost webhook endpoint.
Lambda Configuration (Future Implementation)
For async email dispatch, we'll deploy a Lambda function