Automating Event Calendar Synchronization and Boat Cleaning Dispatch: A Multi-Service Integration Pattern
This post documents a complex infrastructure refactor that consolidated calendar management across multiple booking platforms and automated boat cleaning service dispatch—all triggered from a single Lambda function behind API Gateway. The work involved replacing Google Apps Script polling with event-driven Lambda invocations, integrating GetMyBoat/Boatsetter iCal feeds, and building a Python-based dispatch system for third-party service coordination.
The Problem: Fragmented Calendar Management
The Queen of San Diego event ecosystem previously relied on Google Apps Script (GAS) running time-based triggers to periodically check external booking platforms and manually sync events to Google Calendar. This approach had several drawbacks:
- No real-time synchronization—events could lag by hours or days
- Inefficient polling overhead on platforms with sparse booking activity
- Manual intervention required for platform credential rotation
- Duplicate event handling and conflict resolution done in GAS, not in the source of truth
- Boat cleaning service requests scattered across email and platform dashboards
The refactor centralized calendar operations into a single Lambda function while introducing deterministic dispatch automation for third-party services.
Architecture: Lambda-Driven Event Synchronization
Core Lambda Function
Located at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py, this function serves as the event coordination hub. It exposes multiple actions via API Gateway, each handling a specific synchronization or dispatch task:
add-calendar-event: Insert event into Google Calendar with conflict detectionlist-calendar-events: Retrieve events within date range for dashboard displaysync-getmyboat-ical: Parse iCal feed from GetMyBoat platform and create corresponding calendar entriesdispatch-boat-cleaning: Trigger third-party cleaning service based on booking metadata
Google Calendar API Integration
The Lambda function uses service account credentials stored in AWS Secrets Manager to authenticate with Google Calendar API. Events are created with:
- Summary populated from booking platform (e.g., "Sea Scout Wednesday Hold - 7/12/2025")
- Description field containing booking ID from source platform for reconciliation
- Start/end times parsed from iCal or platform API response
- Custom properties tracking source system (GetMyBoat, Boatsetter, direct booking)
The function checks for existing events before insertion to prevent duplicates—querying by date range and matching on booking ID in the description field.
GetMyBoat and Boatsetter iCal Feed Parsing
Rather than calling REST APIs directly (which require per-platform authentication logic), the integration uses iCal feed URLs provided by both platforms. The Lambda function includes a dedicated handler that:
- Fetches the iCal feed via HTTPS from the platform URL (stored in Secrets Manager)
- Parses the iCalendar format using Python's
icalendarlibrary - Extracts VEVENT objects and maps fields to Google Calendar event schema
- Deduplicates against existing calendar entries by matching UID from iCal
- Batches create requests to reduce API call overhead
This approach is more resilient than REST API polling because iCal is a standardized format with built-in change tracking—platforms update the feed atomically, eliminating the need to query "what changed since last sync."
Boat Cleaning Dispatch Automation
A new Python script at /Users/cb/Documents/repos/tools/dispatch_boat_cleaner.py monitors booking events and automatically triggers cleaning service requests. The workflow:
- Lambda receives a booking confirmation from GetMyBoat webhook or manual API call
- Booking metadata (boat ID, checkout time, customer notes) is extracted
- Dispatch script is invoked asynchronously via Lambda environment variable triggering
- Script formats a service request payload and POSTs to the cleaning platform API
- Confirmation is logged to CloudWatch and returned to the booking platform
Cleaning service credentials and endpoint URLs are stored in AWS Secrets Manager, with rotation handled by Lambda's credential refresh on each invocation. The dispatch script includes retry logic with exponential backoff for transient failures.
API Gateway Routing and Authentication
The Lambda function is exposed via API Gateway v2 (HTTP API for cost efficiency). Route structure:
POST /api/calendar/add-event
POST /api/calendar/list-events
POST /api/calendar/sync-getmyboat
POST /api/boat-services/dispatch-cleaning
Each endpoint is protected by a custom authorization Lambda function that validates an API token (stored in dashboard frontend environment) against a whitelist in Secrets Manager. This avoids IAM complexity while providing audit logging for all API calls.
Data Flow: From Booking to Calendar to Dispatch
The system now follows this sequence:
- Booking Created: Customer books via GetMyBoat platform
- iCal Updated: Platform updates the shared iCal feed atomically
- Dashboard Triggers Sync: Frontend calls
POST /api/calendar/sync-getmyboaton page load or via scheduled fetch - Calendar Entry Created: Lambda parses iCal, detects new VEVENT, inserts into Google Calendar
- Dispatch Triggered: Lambda checks if booking requires cleaning; if yes, invokes dispatch script
- Service Confirmation: Cleaning platform ACKs the request; Lambda logs status to CloudWatch
This event-driven model replaces the previous time-based GAS trigger (which ran every 15 minutes regardless of activity).
Key Implementation Details
CalendarSync.gs Refactor
The original Google Apps Script at /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/CalendarSync.gs still exists but now serves as a fallback. Its polling interval was extended to 12 hours, and all sendEmail notifications were updated to log to Lambda instead. This dual-layer approach provides resilience: if Lambda is unreachable, GAS still syncs on schedule.
Conflict Resolution Strategy
When duplicate events are detected, the system applies this priority:
- If Google Calendar event has a more recent modification time, keep it (user edits are preserved)
- If source platform event is newer, update the calendar entry (reflect platform changes)
- If both are identical, skip (idempotent behavior)
CloudWatch Logging
All Lambda executions log to CloudWatch Logs with structured JSON formatting:
{
"timestamp": "2025-04-28T14:32:00Z",
"action": "sync-getmyboat-ical",
"status": "success",
"events_synced": 3,
"events_skipped": 1,
"dispatch