Automating Boat Cleaning Dispatch and Calendar Sync for Event Operations
This session focused on integrating multiple service platforms—GetMyBoat, Boatsetter, and Google Calendar—into a cohesive event operations workflow. The core challenge: managing boat cleaning logistics for the Queen of San Diego's event schedule without manual coordination overhead.
What Was Done
- Created an automated dispatch system for boat cleaning services (
dispatch_boat_cleaner.py) - Unified Google Calendar event management via AWS Lambda API
- Established bidirectional calendar sync between multiple platforms (CalendarSync.gs)
- Implemented email-based task notifications and confirmations
- Built deployment and scheduling infrastructure for recurring operations
Architecture: Multi-Platform Event Coordination
The system operates across three distinct but interconnected layers:
Layer 1: Dispatch & Service Booking
Created /Users/cb/Documents/repos/tools/dispatch_boat_cleaner.py as the entry point for cleaning service requests. This script interfaces with two boat-sharing platforms:
- GetMyBoat API — Primary platform for boat availability and booking confirmation
- Boatsetter API — Secondary option for redundancy and expanded service coverage
The dispatch logic queries calendar events tagged with a "cleaning required" marker, retrieves available service slots from both platforms, and automatically books the earliest confirmed slot. Credentials are stored in repos.env as GETMYBOAT_API_KEY and BOATSETTER_API_KEY. The script runs via deployment wrapper at /Users/cb/Documents/repos/tools/deploy_boat_cleaner.sh.
Layer 2: Calendar Synchronization
Google Apps Script file CalendarSync.gs (deployed to the GAS replacement project) handles bidirectional sync:
// Example polling structure (not credentials)
function syncCalendars() {
const primaryCal = CalendarApp.getCalendarById(PRIMARY_CAL_ID);
const events = primaryCal.getEvents(new Date(), NEW_DATE(+30));
events.forEach(event => {
if (event.getTag('platform') === 'boatsetter') {
syncToBoatsetter(event);
}
});
}
The script was updated to reduce polling intervals from 15 minutes to 5 minutes for time-sensitive bookings. This trades slightly higher API quota usage for better responsiveness on day-of operations. The deployment uses .clasp.json files (found via search across /Users/cb/Documents/repos/) to map local GAS project directories to their corresponding Google Cloud project IDs.
Layer 3: Lambda API & Dashboard Integration
AWS Lambda function (exact name located via API Gateway route inspection) exposes calendar operations via HTTP API endpoints. The dashboard (/tmp/dashboard_index.html) calls this API with a bearer token stored in repos.env as DASHBOARD_AUTH_TOKEN.
Available actions discovered in Lambda code:
list-calendar-events— Fetch all events within date range, with optional filtering by tagadd-calendar-event— Insert new event with metadata (e.g., service type, location, participants)update-calendar-event— Modify existing event detailssend-notification— Trigger email via SES with event confirmation
Example invocation:
curl -X POST https://[API_GATEWAY_ENDPOINT]/calendar \
-H "Authorization: Bearer $DASHBOARD_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"action": "add-calendar-event",
"title": "Boat Cleaning - Queen of San Diego",
"startTime": "2025-04-28T10:00:00Z",
"endTime": "2025-04-28T12:00:00Z",
"location": "San Diego Harbor",
"tags": {"service": "cleaning", "platform": "getmyboat"}
}'
Key Operational Decisions
Why Dual Platform Integration?
GetMyBoat and Boatsetter have different availability windows and pricing models. By querying both, we maximize booking success rate and reduce cancellation risk. The dispatch script implements a "first confirmed wins" strategy: it books whichever platform responds with availability first, then cancels pending requests on the other platform.
Why Lambda for Calendar?
Google Calendar API credentials are complex to manage across multiple environments. Centralizing them in a single Lambda function (with IAM role restrictions) means:
- No credential sprawl across dashboards, scripts, and CI/CD systems
- Easier audit trail via CloudTrail and Lambda logs
- API Gateway can enforce rate limiting and authentication at the perimeter
- Existing dashboard auth token reuse (single source of truth)
Polling Interval Trade-offs
The 5-minute sync interval was chosen because:
- Boat cleaning windows are typically 2-4 hours (sufficient for 5-min granularity)
- Google Apps Script has a 20,000 request/day quota; 5-min intervals = 288 calls/day, well below limit
- SES email sending (triggered by sync completion) has separate throttling, not impacted
Deployment & Automation
Three shell scripts automate the full stack:
deploy_boat_cleaner.sh— Tests dispatch script locally, runs integration tests against both APIs, then deploys to Lambdadeploy_campaign_scheduler.sh— Syncs CalendarSync.gs to GAS project via clasp CLI- Dashboard deploys independently; calendar state changes are pulled via periodic API calls
Campaign scheduling is configured in /Users/cb/Documents/repos/tools/campaign_schedule.json:
{
"events": [
{
"date": "2025-04-28",
"type": "cleaning",
"platforms": ["getmyboat", "boatsetter"],
"notifyCarole": true
}
]
}
The campaign_scheduler.py reads this file, checks calendar for matching events, and triggers dispatch calls accordingly.
Integration with Existing Systems
All email confirmations go through SES (not direct SMTP), using sender address from repos.env. Carole receives notifications via her configured email on file; dashboard tasks auto-update when confirmations arrive.
Historical data (prior cleaning bookings, cancellations) is logged to CloudWatch Logs under the Lambda function's log group, enabling retrospective analysis and debugging.
What's Next
- Boatsetter Credential Activation — Secondary platform credentials are configured but not yet live; next session should test failover scenarios
- Carole Handoff Documentation — Pending written explainer to Carole on how the system works