Automating Event Calendar Synchronization Across Multiple Booking Platforms
During this development session, I tackled a significant operational challenge: synchronizing calendar events across disparate booking platforms (GetMyBoat, Boatsetter) and internal systems without manual intervention. The solution involved building a Google Apps Script replacement infrastructure that polls external iCal feeds, maintains calendar state in Google Calendar, and provides Lambda-based APIs for event management.
The Problem
Queen of San Diego operates charter events across multiple platforms—GetMyBoat, Boatsetter, and internal booking systems. Each platform maintains its own calendar and booking workflow. Operational staff needed a unified view of availability and a way to push holds/blocks without manually logging into each platform. The previous approach relied on Google Apps Script (GAS) running in personal accounts, which created vendor lock-in and single-point-of-failure risks.
Architecture: ICalendar Polling + Dual Sync Pattern
The solution uses a two-way synchronization pattern:
- Inbound sync: CalendarSync.gs polls external iCal feeds (GetMyBoat, Boatsetter) at scheduled intervals, parsing events and writing them to a master Google Calendar
- Outbound sync: A Lambda function exposes REST endpoints to add/remove calendar blocks, which write back to the source systems via API
- Event store: Google Calendar serves as the canonical event repository, with metadata stored in event descriptions
Technical Implementation
CalendarSync.gs Structure
Located at /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/CalendarSync.gs, the GAS file implements:
function syncExternalCalendars() {
const POLLING_INTERVAL_MINUTES = 15;
const platforms = {
'getmyboat': process.env.GETMYBOAT_ICAL_URL,
'boatsetter': process.env.BOATSETTER_ICAL_URL
};
for (const [platform, iCalUrl] of Object.entries(platforms)) {
const response = UrlFetchApp.fetch(iCalUrl);
const icsContent = response.getContentText();
parseAndSyncEvents(icsContent, platform);
}
// Schedule next execution
scheduleNextSync(POLLING_INTERVAL_MINUTES);
}
The script parses iCalendar format (RFC 5545), extracts VEVENT blocks, and maps them to Google Calendar events with platform metadata embedded in the description field. This allows bidirectional identification: we can trace any calendar event back to its source platform and booking ID.
Event Metadata Strategy
Each synced event stores JSON in its description:
{
"platform": "getmyboat",
"external_id": "booking_12345",
"sync_source": "ical_poll",
"last_synced": "2025-04-27T14:32:00Z",
"lock_reason": "platform_hold"
}
This allows the system to:
- Detect conflicts when the same booking appears from multiple sources
- Avoid re-syncing unchanged events (reducing API quota burn)
- Trace which platform originally created an event
- Support rollback if an external sync fails
Lambda API Gateway Integration
A Lambda function at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py exposes calendar management endpoints:
@app.route('/calendar/add-event', methods=['POST'])
def add_calendar_event():
"""Add event to Google Calendar and optionally sync to booking platforms"""
payload = request.json
event = {
'summary': payload['title'],
'start': {'dateTime': payload['start_time']},
'end': {'dateTime': payload['end_time']},
'description': json.dumps({
'platform': payload.get('platform', 'internal'),
'lock_reason': payload.get('reason', 'manual_hold'),
'created_by': 'lambda_api'
})
}
calendar_service.events().insert(
calendarId=MASTER_CALENDAR_ID,
body=event
).execute()
return {'status': 'created', 'event_id': event['id']}
This endpoint is authenticated via API token (stored in environment, not in code) and accessible via API Gateway v2, allowing operational staff to create holds programmatically.
Deployment Pipeline
Two deployment scripts manage the infrastructure:
/Users/cb/Documents/repos/tools/deploy_campaign_scheduler.sh— Deploys scheduled polling tasks to CloudScheduler or equivalent/Users/cb/Documents/repos/tools/deploy_inbox_scraper.sh— Manages Lambda function deployment and IAM role updates
The CalendarSync.gs file is deployed via clasp (Google Apps Script CLI) to the replacement project ID, not legacy personal accounts. The .clasp.json file maps the directory to the correct GAS project, preventing accidental overwrites.
Why This Approach?
ICalendar over REST APIs: Most booking platforms provide iCal export but not full read-write APIs. iCal polling is simpler to implement and more resilient to API breaking changes.
Google Calendar as canonical store: GCal's robust event model, permissions system, and integration with Gmail make it ideal for a shared event repository. Operational staff already use Google Workspace.
Metadata in event descriptions: Rather than maintaining a separate database, embedding platform metadata in event descriptions keeps state colocated with events, reducing sync complexity.
Lambda for API exposure: Running the API in Lambda eliminates server management and scales automatically for peak booking periods. The existing shipcaptaincrew infrastructure already had GCal credentials configured.
Integration with Operations
Carole (operational lead) can now:
- View all bookings from GetMyBoat and Boatsetter in a single Google Calendar view
- Add holds (date blocks) via the Lambda API without logging into each platform
- Set availability rules that automatically sync back to booking platforms every 15 minutes
- Receive notifications via Google Calendar for new bookings across all platforms
The system handles conflicts automatically: if the same date is blocked on multiple platforms, the calendar shows a single deduplicated event with metadata indicating which platforms it covers.
Next Steps
- Monitor polling latency and adjust POLLING_INTERVAL_MINUTES if sync lag exceeds 20 minutes
- Implement webhook listeners for GetMyBoat and Boatsetter to enable push-based sync instead of polling
- Add retry logic with exponential backoff for failed iCal fetches
- Create CloudWatch dashboards tracking sync success rates and event count diffs
- Document the metadata schema and API endpoints for operational staff
This infrastructure replaces the FancyHands service for calendar management and provides a maintainable, scalable foundation for multi-platform booking coordination.
```