Building a Multi-Tenant Event Synchronization System: CalendarSync.gs and Boat Booking Integration
This session focused on consolidating event management across multiple platforms: Google Calendar, boat booking services (GetMyBoat, Boatsetter), and internal scheduling systems. The core challenge was synchronizing calendar data bidirectionally while maintaining separation between business domains (venue events, boat cleanings, scout activities).
What Was Done
We completed three major infrastructure components:
- Enhanced
CalendarSync.gs— the Google Apps Script file managing calendar synchronization logic - Created
dispatch_boat_cleaner.py— a Python tool for scheduling boat maintenance tasks - Built
platform_inbox_scraper.pyandcampaign_scheduler.py— email and event automation tools
The underlying architecture moved from manual task scheduling to an automated event pipeline that ingests booking data, synchronizes calendar state, and triggers downstream workflows.
CalendarSync.gs: The Synchronization Engine
CalendarSync.gs lives in the Google Apps Script project replacement directory at:
/Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/CalendarSync.gs
This file handles bidirectional synchronization between Google Calendar and external booking platforms. The architecture uses:
- Polling mechanism — Retrieves calendar events at configurable intervals (currently checking for changes every N minutes)
- Event state tracking — Maintains a record of synced events to detect additions, modifications, and deletions
- Platform-specific adapters — Separate logic branches for GetMyBoat iCal feeds, internal calendar holds, and automated boat cleaning schedules
- Email notifications — Triggers
sendEmail()calls to stakeholders when events are added or modified
Key function names we worked with:
onOpen() // Initialize script UI triggers
syncCalendarEvents() // Main polling loop
parseICalFeed() // Parse external iCal from boat platforms
addEventToCalendar() // Insert events into Google Calendar
notifyStakeholders() // Send email notifications
Why this approach: Google Calendar is the single source of truth. By syncing external bookings into it, we avoid maintaining separate event databases. The polling model (rather than webhooks) was chosen because GetMyBoat and Boatsetter don't offer reliable push notifications for iCal changes, and Google Apps Script's time-based triggers are sufficient for the business velocity we're operating at.
Boat Cleaner Dispatch System
Created at:
/Users/cb/Documents/repos/tools/dispatch_boat_cleaner.py
This Python utility reads boat maintenance events from a calendar source and dispatches cleaning tasks. The workflow:
- Reads Google Calendar for boat-related events (filtered by label or description)
- Extracts location, time, and boat ID metadata
- Creates dispatch records with contractor assignments
- Logs task status to
campaign_schedule.jsonfor auditing
The script uses environment variables (loaded from repos.env) to authenticate with:
- Google Calendar API (service account credentials)
- Internal task tracking system
- Email system (SES) for contractor notifications
Why Python over GAS: This logic needed to live outside the Apps Script environment because it coordinates multiple external systems. Python's ecosystem (requests, boto3, google-auth) made cross-platform integration cleaner. It runs on a Lambda function or scheduled EC2 instance, separate from the synchronization layer.
Campaign and Event Scheduling Infrastructure
Built two complementary systems:
Campaign Scheduler (campaign_scheduler.py):
/Users/cb/Documents/repos/tools/campaign_scheduler.py
Manages email blast campaigns with template composition. Reads from:
/Users/cb/Documents/repos/tools/campaign_schedule.json
Which contains scheduling metadata (send times, recipient segments, template references). The system:
- Hydrates HTML templates from
/tools/templates/with dynamic content - Batches sends via AWS SES
- Tracks delivery status and bounces
- Supports A/B variant scheduling (rady_shell_blast1.html vs. blast2.html)
Platform Inbox Scraper (platform_inbox_scraper.py):
/Users/cb/Documents/repos/tools/platform_inbox_scraper.py
Listens to incoming messages from booking platforms (GetMyBoat, Boatsetter, et al.) via Gmail API. When new inquiries arrive, it:
- Extracts booking metadata (dates, vessel, party size)
- Validates against calendar hold conflicts
- Routes responses through appropriate business logic
- Logs interactions to platform-specific dashboards
Why separate systems: Campaign scheduling and inbox scraping have different trigger models — campaigns are time-driven (cron), inbox scraping is event-driven (email arrival). Decoupling them prevents resource contention and allows independent scaling.
Infrastructure and Deployment
Google Apps Script:
CalendarSync.gs is deployed via .clasp.json configuration. Located at:
/Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/.clasp.json
The scriptId maps to the GAS project. Updates are pushed via:
clasp push
This compiles the TypeScript/JavaScript and deploys to Google's infrastructure, making functions immediately available to calendar triggers.
Python deployment:
Both Python scripts have corresponding deployment shells:
/Users/cb/Documents/repos/tools/deploy_campaign_scheduler.sh
/Users/cb/Documents/repos/tools/deploy_inbox_scraper.sh
These handle:
- Environment variable injection (secrets from
repos.env) - Dependency installation (pip requirements)
- Lambda function updates or EC2 task scheduling
- CloudWatch log group configuration
Why Lambda over EC2: For event-driven work (inbox scraping), Lambda's pay-per-invocation model is more cost-efficient. Time-based campaigns use CloudWatch Events to trigger Lambda on schedule — simpler than managing cron jobs on persistent compute.
Key Architectural Decisions
1. Calendar as source of truth — Rather than maintaining event state in multiple databases, Google Calendar becomes the system of record. This simplifies consistency guarantees.