Building a Maintenance Task Notification System with GAS, Lambda, and Real-Time Email Alerts
The Problem
The maintenance team at JADA Sailing operates a critical tool at maintenance.queenofsandiego.com where technicians log completed work and ongoing maintenance tasks. However, when new tasks were added by team members like Travis, there was no mechanism to notify the operations team. Sergio needed visibility into new tasks as they were created, but the notification strategy—individual alerts versus daily digests—depended on task criticality.
We needed a solution that surfaced new tasks in real-time while respecting team preferences for notification frequency based on urgency. This required building a multi-layer notification pipeline with persistence, routing, and intelligent batching.
Architecture Overview
The solution spans three main components:
- Frontend (Staging HTML): Modified
/tools/maintenance/staging-index.htmlto capture task submissions with criticality metadata - Backend (Google Apps Script): Added maintenance routing to
BookingAutomation.gsand createdMaintenancePersistence.gsfor data handling - Notification Engine (AWS Lambda): Serverless function to process tasks, evaluate criticality, and dispatch emails with appropriate urgency levels
Frontend Changes: Task Metadata Capture
The maintenance tool's HTML interface needed enhancement to classify tasks by criticality. We modified staging-index.html to include a severity selector in the task submission form:
// In the task submission form:
<select name="criticality" id="taskCriticality">
<option value="low">Low - Document for daily review</option>
<option value="medium" selected>Medium - Notify within 4 hours</option>
<option value="critical">Critical - Immediate notification</option>
</select>
This captures the semantic intent behind each task, allowing downstream systems to make intelligent routing decisions without parsing natural language.
Google Apps Script: Maintenance Route Handler
We added a new maintenance action route to the existing BookingAutomation.gs doPost handler. The action routing pattern already existed for other operations (bookings, payments, etc.), so we extended it:
// In BookingAutomation.gs doPost()
if (action === 'log_maintenance') {
return MaintenancePersistence.handleNewTask(
{
task_description: e.parameter.task_description,
completed_by: e.parameter.completed_by,
criticality: e.parameter.criticality || 'medium',
timestamp: new Date(),
work_category: e.parameter.work_category
}
);
}
This keeps the main dispatcher clean while delegating maintenance-specific logic to a dedicated module.
Persistence Layer: MaintenancePersistence.gs
We created a new GAS file specifically for maintenance data handling. This module:
- Validates incoming task data
- Stores tasks in a Google Sheet (for audit trail and spreadsheet-based reporting)
- Triggers the Lambda notification pipeline via HTTP
- Returns confirmation to the frontend
// MaintenancePersistence.gs excerpt
function handleNewTask(taskData) {
const sheet = SpreadsheetApp.getActive()
.getSheetByName('Maintenance_Tasks');
sheet.appendRow([
taskData.timestamp,
taskData.task_description,
taskData.completed_by,
taskData.criticality,
taskData.work_category,
'pending_notification'
]);
// Trigger Lambda notification
notifyMaintenanceTeam(taskData);
return ContentService.createTextOutput(
JSON.stringify({success: true, task_id: newRowIndex})
).setMimeType(ContentService.MimeType.JSON);
}
function notifyMaintenanceTeam(taskData) {
const lambdaEndpoint = PropertiesService.getScriptProperties()
.getProperty('MAINTENANCE_LAMBDA_URL');
const payload = {
task: taskData,
source: 'maintenance-tool',
environment: 'staging'
};
UrlFetchApp.fetch(lambdaEndpoint, {
method: 'post',
payload: JSON.stringify(payload),
contentType: 'application/json'
});
}
This separation of concerns makes testing easier and allows the persistence layer to evolve independently from the main booking automation logic.
Infrastructure: Lambda and Email Routing
The notification decision logic lives in a Lambda function (matching existing patterns used for tips-box processing). The function examines task criticality and decides whether to:
- Immediate notification (critical tasks): Email sent directly to Sergio and the maintenance Slack channel
- Batched notification (medium/low): Queued for daily digest at 6 PM PT via CloudWatch Events
We configured the Lambda function with an IAM role permitting ses:SendEmail and dynamodb:PutItem (for batch queue storage).
Email Configuration and Staging Testing
Following AWS best practices, we configured email sending through Amazon SES with:
- From address: An operations alias (created in the organization's email system)
- To address (staging):
jadasailing@gmail.comfor team review before production - Reply-to: Maintenance ticket system (when available)
We created a Google Calendar called "Jada Maintenance" in the jadasailing@gmail.com account to cross-reference maintenance tasks with crew availability and other operations events.
Deployment and CloudFront Invalidation
Changes were deployed in this order:
// Step 1: Deploy modified staging HTML to S3
aws s3 cp staging-index.html \
s3://maintenance-queenofsandiego-staging/index.html
// Step 2: Invalidate CloudFront cache for staging distribution
aws cloudfront create-invalidation \
--distribution-id [STAGING_DIST_ID] \
--paths "/index.html"
// Step 3: Push GAS changes (BookingAutomation.gs + MaintenancePersistence.gs)
clasp push
// Step 4: Verify GAS deployment
clasp status
The staging environment now fully separates from production, allowing Sergio to test notification timing and email formatting without affecting live operations.
Key Design Decisions
Why Lambda for notifications? GAS has rate limits and no native batch queuing. Lambda scales independently, supports scheduled invocations via CloudWatch Events, and integrates seamlessly with SES for high-volume email operations.
Why capture criticality at submission time? Operators understand context better than algorithms. A technician logging a task knows whether it's a routine maintenance note or a safety-critical issue. Rather than parsing descriptions, we trust domain expertise.
Why use a Google Sheet for persistence? The team already reviews maintenance logs in Sheets. Storing directly to the sheet provides immediate audit trail visibility, enables manual filtering/sorting by stakeholders, and