Automating Event Calendar Synchronization and Service Dispatch: Building a Multi-Platform Integration Layer
This session tackled a critical operational challenge: replacing manual event management with an automated calendar synchronization system while simultaneously building a service dispatch pipeline. The work spans Google Apps Script, Python automation, AWS Lambda, and API Gateway integration.
The Problem Statement
The organization manages events across multiple platforms (Google Calendar, GetMyBoat, Boatsetter, external booking sites) with no unified source of truth. Calendar events were being manually entered, email campaigns lacked coordination with actual availability, and service scheduling (boat cleaning, catering logistics) happened through disconnected channels. The FancyHands outsourcing attempt failed, forcing a return to in-house dispatch automation.
Architecture: Calendar Sync via Google Apps Script
The core solution is a Google Apps Script project deployed to production. Located at:
/Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/CalendarSync.gs
This script performs several critical functions:
- iCal polling: Retrieves calendar feeds from GetMyBoat and Boatsetter via their respective iCal endpoints
- Event normalization: Transforms platform-specific event formats into a unified schema
- Google Calendar syncing: Writes normalized events to a master Google Calendar via the Calendar API
- Email notifications: Sends alerts when availability changes affect operations
- Scheduled execution: Runs on a configurable interval (currently hourly) via Apps Script time-based triggers
The script was updated to support multiple action types via a Lambda-friendly API pattern. Instead of a monolithic trigger function, CalendarSync.gs now exposes named actions that can be invoked both from time-based triggers and from external HTTP calls via API Gateway.
Dispatch Automation: Python and Shell Integration
Three new Python tools were created to handle service dispatch:
/Users/cb/Documents/repos/tools/dispatch_boat_cleaner.py— Reads boat availability from the synced calendar, determines cleaning windows, and creates dispatch tickets/Users/cb/Documents/repos/tools/platform_inbox_scraper.py— Polls email inboxes (via Gmail API) for booking inquiries and automatically routes them to the dispatch system/Users/cb/Documents/repos/tools/campaign_scheduler.py— Coordinates email campaigns with actual calendar availability, preventing overbooking scenarios
Each tool is deployed via shell wrapper scripts:
#!/bin/bash
# deploy_inbox_scraper.sh
PYTHON_RUNTIME="/usr/local/bin/python3"
SCRIPT_PATH="/Users/cb/Documents/repos/tools/platform_inbox_scraper.py"
LOG_FILE="/var/log/platform_inbox_scraper.log"
$PYTHON_RUNTIME $SCRIPT_PATH >> $LOG_FILE 2>&1
Why this approach? Shell wrappers allow:
- Consistent logging and error handling
- Integration with system cron (for recurring execution)
- Easy credential injection via environment variables (loaded from
repos.env) - Graceful failure recovery without disrupting the main application
Email Campaign Templates and Data
Campaign scheduling required standardized HTML templates:
/Users/cb/Documents/repos/tools/templates/rady_shell_blast1.html/Users/cb/Documents/repos/tools/templates/rady_shell_blast2.html/Users/cb/Documents/repos/sites/quickdumpnow.com/marketing/templates/qdn_blast1.html
Campaign execution is controlled via campaign_schedule.json:
{
"campaigns": [
{
"id": "rady_shell_spring_2024",
"template": "rady_shell_blast1.html",
"send_time": "2024-04-28T09:00:00Z",
"audience_segment": "active_members",
"calendar_dependency": "availability_confirmed"
}
]
}
The campaign_scheduler.py reads this configuration and blocks sends until the calendar dependency (e.g., "availability_confirmed") transitions to true. This prevents sending marketing material for sold-out events.
Lambda Function Enhancement for Calendar Operations
The existing Lambda function at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py was extended with calendar event management capabilities.
Rather than modifying the core function, we added a dispatcher pattern:
def lambda_handler(event, context):
action = event.get('action')
if action == 'add-calendar-event':
return add_calendar_event(event)
elif action == 'list-calendar-events':
return list_calendar_events(event)
elif action == 'get-availability':
return get_availability(event)
# ... other actions
return {'statusCode': 400, 'body': 'Unknown action'}
Why a dispatcher? It allows:
- Multiple operations per Lambda invocation (reducing cold starts)
- Consistent authentication across all calendar operations
- Easy testing of individual actions
- Clear separation of concerns without code duplication
Google Calendar API credentials are stored in Lambda environment variables and loaded at function initialization (not on every invocation) for performance.
API Gateway Routing for Calendar Operations
API Gateway (v2, HTTP API) was configured to expose the Lambda dispatcher at:
POST /api/calendar/{action}
Authorization: Bearer {dashboard_token}
Content-Type: application/json
Example invocation to add 7 Sea Scout Wednesday holds:
curl -X POST https://api.internal.example.com/api/calendar/add-calendar-event \
-H "Authorization: Bearer $DASHBOARD_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Sea Scout Wednesday Hold",
"start": "2024-05-01T18:00:00Z",
"end": "2024-05-01T20:00:00Z",
"recurrence": "WEEKLY",
"count": 7,
"calendar_id": "primary"
}'
This pattern avoids hardcoding scheduling logic in dashboards or one-off scripts. All calendar operations flow through a single, auditable endpoint.
Site-Specific Dashboard and Unsubscribe Handling
Two separate dashboard implementations were updated:
/tmp/dashboard_index.html— Internal operations dashboard/tmp/carole_index.html— External-facing Carole event management dashboard
An unsubscribe page was created for quickdumpnow.com:
/Users/cb/Documents/