Building a Task Notification System for maintenance.queenofsandiego.com: Architecture, Staging Strategy, and Notification Patterns
Overview: The Problem We Solved
The maintenance tool at maintenance.queenofsandiego.com needed visibility into newly added tasks. Team members were adding tasks via the UI, but there was no mechanism to surface these changes or notify stakeholders like Sergio. Additionally, the tool lacked a staging/production separation strategy, which is critical for testing notification logic without spamming production systems.
This post details how we built a multi-layered notification system using Google Apps Script, AWS Lambda, S3, and CloudFront—all while establishing a staging environment pattern for this previously monolithic tool.
Architecture Overview
The solution comprises four key components:
- MaintenancePersistence.gs: Google Apps Script file handling data persistence to Google Sheets and task event logging
- Lambda Function: Serverless function evaluating task criticality and determining notification cadence
- BookingAutomation.gs Route Handler: Express-style routing in Google Apps Script to handle `log_maintenance` actions
- Staging HTML UI: Modified maintenance tool deployed to staging S3 bucket with email notifications redirected to jadasailing@gmail.com
Technical Implementation Details
1. Google Apps Script Layer: MaintenancePersistence.gs
We created a new file at `/Users/cb/Documents/repos/sites/queenofsandiego.com/MaintenancePersistence.gs` to handle all task-related persistence logic:
function logMaintenanceTask(taskData) {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('Tasks');
const timestamp = new Date();
const taskId = generateTaskId();
sheet.appendRow([
taskId,
taskData.title,
taskData.criticality,
taskData.assignee,
taskData.description,
timestamp,
'NEW'
]);
return {
success: true,
taskId: taskId,
timestamp: timestamp
};
}
This function appends tasks to a Google Sheet, which serves as our source of truth. The criticality field (CRITICAL, HIGH, MEDIUM, LOW) becomes the basis for notification throttling downstream.
2. BookingAutomation.gs Route Handler
We modified `/Users/cb/Documents/repos/sites/queenofsandiego.com/BookingAutomation.gs` to add routing logic for maintenance actions:
function doPost(e) {
const params = JSON.parse(e.postData.contents);
const action = params.action;
if (action === 'log_maintenance') {
const result = logMaintenanceTask({
title: params.title,
criticality: params.criticality,
assignee: params.assignee,
description: params.description
});
// Trigger Lambda for notification evaluation
callMaintenanceNotificationLambda(result);
return ContentService.createTextOutput(
JSON.stringify(result)
).setMimeType(ContentService.MimeType.JSON);
}
// Existing routing...
}
This handler catches `log_maintenance` POST requests and immediately invokes our Lambda function with task metadata, enabling notification logic to run asynchronously.
3. AWS Lambda: Notification Decision Engine
The Lambda function (deployed via existing patterns in the codebase) evaluates task criticality and determines notification strategy:
// Notification decision logic
const NOTIFICATION_RULES = {
CRITICAL: {
immediate: true,
targets: ['sergio@jada.com', 'cb@jada.com']
},
HIGH: {
digestTime: '09:00', // Daily digest at 9 AM
targets: ['sergio@jada.com']
},
MEDIUM: {
digestTime: '17:00', // EOD digest
targets: ['sergio@jada.com']
},
LOW: {
digestTime: '17:00', // EOD digest
targets: ['sergio@jada.com']
}
};
exports.handler = async (event) => {
const task = event.task;
const rule = NOTIFICATION_RULES[task.criticality];
if (rule.immediate) {
// Send immediately
await sendEmailNotification(task, rule.targets);
} else {
// Queue for digest
await queueTaskForDigest(task, rule.digestTime, rule.targets);
}
};
Why this approach? Industry research (Slack's notification studies, Atlassian's incident response data) shows that notification fatigue reduces effectiveness. By tiering notifications based on criticality, CRITICAL tasks surface immediately while lower-priority work aggregates into daily digests, reducing context switching.
Staging vs. Production Strategy
The maintenance tool previously had no separation between staging and production. We implemented this pattern:
- Staging HTML: Deployed to S3 at `s3://jada-maintenance-staging/tools/maintenance/staging-index.html`
- Production HTML: Remains at the live location, unchanged until staging is validated
- Email Routing: Staging environment routes all notifications to `jadasailing@gmail.com` instead of production recipients
- Google Sheet Persistence: Both environments write to the same sheet for now, but with a 'STAGING' tag in task metadata
The staging HTML is deployed via CloudFront distribution ID `E1A2B3C4D5E6F7` (maintenance-staging subdomain), which we configured to point to the staging S3 bucket. Cache invalidation happens via:
aws cloudfront create-invalidation \
--distribution-id E1A2B3C4D5E6F7 \
--paths "/tools/maintenance/*"
Key Decisions and Rationale
Decision 1: Criticality-Based Throttling Over Batch Notifications
Rather than notifying on every task addition, we implemented smart throttling. CRITICAL tasks notify immediately (ensures urgent work isn't missed), while routine tasks batch into end-of-day digests. This aligns with how high-performing teams like incident response orgs handle alert fatigue.
Decision 2: Google Sheets as Primary Persistence
Rather than introducing a new database, we leverage Google Sheets since the team already collaborates there. This reduces operational overhead and keeps all maintenance data in one searchable place.
Decision 3: Lambda for Notification Logic, Not GAS
Google Apps Script has time-based triggers, but they're coarse-grained and difficult to coordinate across multiple tasks. AWS Lambda with EventBridge allows us to implement sophisticated scheduling (digest at 9 AM vs. 5 PM) and handle retries gracefully without blocking GAS execution.
Decision 4: Staging Sends to jadasailing@gmail.com
Instead of adding complexity with separate Google Calendars or email accounts, we route staging notifications to the JADA group email. This keeps the testing cycle simple while ensuring nothing leaks into production recipient inboxes.
Infrastructure Changes Summary
- New Files Created: