```html

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 entry
  • GET /calendar/list-events — Retrieve events for date range
  • PATCH /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