Automating Boat Cleaning Dispatch and Calendar Synchronization Across Multiple Event Platforms
This session focused on solving a critical operational gap: coordinating boat cleaning services across multiple event booking platforms (GetMyBoat, Boatsetter) and synchronizing those schedules with Google Calendar via Google Apps Script. The solution integrates Python-based dispatch automation with real-time calendar syncing to eliminate manual coordination overhead.
The Problem
Event scheduling for boat-based services requires coordination across multiple platforms:
- Event bookings come in via GetMyBoat and Boatsetter APIs
- Manual email notifications were being sent to cleaning crews
- No centralized view of upcoming cleaning needs
- Calendar data wasn't syncing back to Google Calendar for visibility
- Crew scheduling was fragmented across email and chat
The immediate need: automate dispatch notifications and ensure calendar visibility for the 4/28 event season.
Solution Architecture
Core Components
1. Boat Cleaner Dispatch Script
Created /Users/cb/Documents/repos/tools/dispatch_boat_cleaner.py to:
- Poll GetMyBoat and Boatsetter APIs for upcoming bookings
- Filter by date range and boat availability
- Generate structured dispatch notifications
- Send via SES (Amazon Simple Email Service) to crew members
The script uses environment variables for credentials (loaded from repos.env) and generates a standardized notification format that includes:
- Booking ID and platform source
- Boat name, capacity, and location
- Event date/time and estimated duration
- Crew assignments and contact info
- Pre-cleaning checklist
python dispatch_boat_cleaner.py \
--date 2024-04-28 \
--platforms getmyboat boatsetter \
--dry-run # validate without sending
2. Google Apps Script Calendar Synchronization
Enhanced /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/CalendarSync.gs to:
- Receive calendar events via HTTP POST from Lambda functions
- Parse event details and validate against existing calendar
- Create or update Google Calendar entries in near-real-time
- Support recurring events (Sea Scout Wednesday holds)
- Implement exponential backoff for API rate limiting
The CalendarSync.gs file uses Google Apps Script's doPost() handler to accept JSON payloads:
// Example event payload format
{
"action": "add-calendar-event",
"calendarId": "calendar@group.calendar.google.com",
"event": {
"title": "Sea Scout Wednesday Hold",
"start": "2024-05-01T18:00:00-07:00",
"end": "2024-05-01T20:00:00-07:00",
"description": "Weekly boat cleaning prep",
"attendees": ["crew@example.com"]
}
}
3. Lambda-based Calendar API
The Lambda function at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py serves as the orchestration layer:
- Exposes REST endpoints via API Gateway v2
- Validates incoming requests using dashboard token authentication
- Routes to appropriate GAS handler (add-event, list-events, update-event)
- Implements request queuing to prevent calendar API throttling
- Logs all operations for audit trails
The Lambda endpoint is deployed to API Gateway with routes like:
POST /calendar/add-event— Create new calendar entryGET /calendar/list-events— Retrieve events for date rangePATCH /calendar/update-event— Modify existing event
Integration Pattern
Data Flow:
- Boat platform APIs (GetMyBoat/Boatsetter) → Dispatch script polling
- Dispatch script → SES email notifications to crew
- Dispatch script → Lambda calendar API POST
- Lambda → Google Apps Script doPost() handler
- GAS → Google Calendar API (OAuth-authenticated service account)
This decoupling allows the dispatch system to function independently while calendar sync can be retried asynchronously if the GAS endpoint is temporarily unavailable.
Key Technical Decisions
Why Google Apps Script instead of direct Google Calendar API?
- GAS handles OAuth token refresh automatically
- Service account credentials stay within Google's ecosystem (more secure)
- Built-in rate limiting and quota management
- Can be deployed without exposing Lambda environment to Google credentials
Why polling instead of webhooks?
- GetMyBoat and Boatsetter don't support consistent webhook delivery
- Polling allows us to control retry logic and backoff strategies
- Simpler deployment — no need to expose our Lambda to inbound requests from third parties
- Can batch process multiple bookings in a single run
Why Lambda instead of a scheduled EC2 instance?
- Cost efficiency: pay only for execution time
- Auto-scaling: handles traffic spikes during event seasons
- Simplified deployment: no instance management or patching
- CloudWatch integration: native logging and monitoring
Infrastructure
AWS Resources Used:
- Lambda Function:
rady-shell-events-calendar-api(Python 3.11 runtime) - API Gateway v2: HTTP API with OAuth/token-based authorization
- IAM Role: Permissions for CloudWatch Logs, SES, Secrets Manager (Google credentials)
- CloudWatch: Logs for all dispatch and calendar operations
- SES: Verified sender for crew notification emails
Google Cloud Resources:
- Service Account: Calendar API access (scopes:
calendar,calendar.events) - Google Calendar: Shared calendar for boat events (shared with crew)
- Apps Script Project: Deployed as web app with service account authentication
Deployment and Testing
Created deployment script at /Users/cb/Documents/repos/tools/deploy_boat_cleaner.sh:
#!/bin/bash