Automating Event Scheduling and Service Dispatch: Replacing Google Apps Script with Lambda-Driven Calendar APIs
What Was Done
During this session, we migrated event synchronization and scheduling workflows away from fragile Google Apps Script polling mechanisms toward a robust Lambda-driven calendar API architecture. The primary achievements were:
- Deployed a new
CalendarSync.gsreplacement that uses direct Lambda invocation instead of timed triggers - Created a centralized calendar event API endpoint backed by Lambda, eliminating polling delays and reducing Google API quota pressure
- Built a dispatch automation system for service coordination tasks (boat cleaning scheduling)
- Integrated calendar operations into the existing dashboard infrastructure for real-time status tracking
Technical Architecture: From Polling to Event-Driven
The original implementation relied on Google Apps Script's time-based triggers, which polled external calendar sources at fixed intervals. This approach had three critical problems:
- Latency: Events could take up to 30 minutes to sync depending on polling interval
- API quota exhaustion: Repeated polling burned Google Calendar API quotas even when no changes occurred
- Operational invisibility: No visibility into sync failures or trigger execution within the dashboard
The new architecture inverts this model. Instead of Apps Script polling, we:
- Expose calendar operations through a Lambda function with explicit action names
- Call Lambda directly from our dashboard via API Gateway endpoints
- Track all calendar state changes in the dashboard task system
- Eliminate polling in favor of on-demand invocation
Infrastructure Changes
Lambda Function Updates
The existing Lambda function (name abstracted for security) was enhanced to support multiple calendar actions. Key actions implemented:
add-calendar-event— Adds single or batch events to Google Calendarlist-calendar-events— Queries calendar within date range, returns JSONsync-external-calendar— Imports events from iCal feeds (GetMyBoat, Boatsetter schedules)remove-calendar-event— Deletes events by ID
Each action accepts a JSON payload with parameters like eventName, startTime, endTime, calendarId, and externalSource.
API Gateway Integration
The Lambda function is exposed via API Gateway v2 endpoints. The calendar operations endpoint structure:
POST /api/v1/calendar/{action}
Headers: Authorization: Bearer {dashboard-token}
Body: {
"action": "add-calendar-event",
"eventName": "Sea Scout Wednesday Hold",
"startTime": "2025-04-30T18:00:00Z",
"endTime": "2025-04-30T19:30:00Z",
"calendarId": "primary"
}
This replaces the previous pattern of calling Apps Script endpoints with URLFetchApp.
Google Calendar API Credentials
Credentials are stored in the Lambda environment (not in GAS), using service account authentication. This centralizes credential management and enables rotation without updating multiple GAS projects.
CalendarSync.gs Replacement Strategy
The file /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/CalendarSync.gs was updated to act as an administrative interface rather than a polling daemon:
- Removed: All
setTrigger()calls and time-based execution handlers - Removed: Direct
Calendar.Events.insert()calls (now delegated to Lambda) - Added: Functions that construct properly-formatted payloads for the Lambda API
- Added: Request logging that records sync attempts in a Google Sheet for audit trails
The key decision here: GAS now serves as a UI for manually triggering syncs or viewing logs, not as an autonomous scheduler. This improves debuggability because every calendar action is explicitly logged.
Service Dispatch System: Boat Cleaning Example
To handle operational tasks like boat cleaning coordination, we created:
dispatch_boat_cleaner.py
This Python script sits in /Users/cb/Documents/repos/tools/ and handles the workflow:
- Reads boat availability from GetMyBoat/Boatsetter iCal feeds via the Lambda
sync-external-calendaraction - Identifies gaps in the cleaning schedule
- Generates dispatch instructions with time windows and contact info
- Logs dispatch records to CloudWatch for downstream integration
deploy_inbox_scraper.sh
A deployment wrapper that:
#!/bin/bash
# Load environment (repos.env contains API endpoints, tokens)
source /Users/cb/Documents/repos/repos.env
# Invoke dispatch script
python3 /Users/cb/Documents/repos/tools/dispatch_boat_cleaner.py \
--calendar-api-endpoint "$CALENDAR_API_URL" \
--auth-token "$DASHBOARD_TOKEN" \
--output-format json
# Log results to dashboard
curl -X POST "$DASHBOARD_API_URL/tasks/t-boat-dispatch/notes" \
-H "Authorization: Bearer $DASHBOARD_TOKEN" \
-d '{"note": "Dispatch script executed, see CloudWatch logs"}'
This script is typically triggered by a cron job or manual invocation when boat schedules need reconciliation.
Dashboard Integration
Calendar events are now surfaced in the dashboard task system. When a Lambda calendar action succeeds, the response includes:
- Event IDs (for future deletion/modification)
- Sync timestamps
- Error details if the action failed
This information is logged to task cards (e.g., t-21de9456, t-a69ba26b) so that stakeholders see the status without querying Google Calendar directly.
Key Decisions and Rationale
Why Lambda Over Apps Script for Calendar Sync?
Apps Script is excellent for UI automation and Google Workspace integration, but poor for backend orchestration. Lambda gives us:
- Proper error handling and retry logic (CloudWatch visibility)
- Credential isolation from GAS audit logs
- Scalability for batch operations (adding 50 events in parallel)
- Integration with existing dashboard authentication (IAM roles)
Why iCal Polling via Lambda Instead of Direct OAuth?
GetMyBoat and Boatsetter expose schedules via iCal feeds (read-only URLs). While less "pure" than OAuth, this approach:
- Requires zero account-specific credentials (feed URL only)
- Avoids OAuth token rotation complexity
- Reduces