Multi-Tenant Calendar Sync & Guest Portal Automation: Building Reliable Event Handoffs Across Google Calendar, DynamoDB, and S3
What Was Done
This session focused on establishing a reliable handoff pipeline for weekend charter events across three integrated systems: Google Calendar (source of truth), DynamoDB (crew dispatch state), and S3 (guest-facing pages). The core challenge was ensuring consistency when the same event exists in multiple places—calendar details change, but guest pages and crew assignments must stay synchronized without manual intervention.
Specifically, we:
- Diagnosed and fixed crew roster inconsistencies on the JADA Internal calendar for three weekend charters
- Rebuilt guest page templates and redeployed to S3 with CloudFront invalidation
- Implemented DynamoDB event record creation/update logic for crew dispatch
- Tested the full waiver + photo upload flow for both adult and minor guests
- Orchestrated end-to-end contact sync (email, phone, guest metadata) via automated boarding emails
Technical Details: Calendar Sync Architecture
The Problem: Google Calendar (via iCal feed) served as the authoritative source, but crew assignments were incomplete. The JADA Internal calendar's iCal endpoint returned event descriptions in plain text, making it difficult to parse structured crew data programmatically. Additionally, calendar patches needed to flow through to both the Google Calendar API and downstream systems (DynamoDB, guest pages).
Solution: Layered Fetch Strategy
We implemented three parallel data-fetch methods:
- iCal parsing (
/tmp/jada-internal.ics) — Fast, cacheable, returns all events; used for bulk listing and initial sync checks - Google Calendar API direct fetch — Higher latency but returns full event objects with nested resource IDs; used for patching crew assignments
- DynamoDB crew dispatch table — Single source of truth for runtime crew state and guest waiver status
Why three layers? iCal is fast and doesn't require active OAuth tokens (useful for CI/CD), but it strips resource IDs needed for API patches. Google Calendar API gives us those IDs but is slower. DynamoDB decouples calendar changes from runtime state—crew assignments can be overridden at dispatch time without touching the calendar.
Key Commands Used:
# Fetch iCal (no auth required, but cached)
curl -s 'https://calendar.google.com/calendar/ical/.../public.ics' | grep -A 20 'VEVENT'
# Fetch via Google Calendar API (requires valid OAuth token)
curl -s -H "Authorization: Bearer $TOKEN" \
'https://www.googleapis.com/calendar/v3/calendars/.../events?q=quinn' \
| jq '.items[] | {id, summary, start, description}'
# Query DynamoDB for event crew state
aws dynamodb get-item \
--table-name crew-dispatch \
--key '{"event_id": {"S": "quinn-may-30"}}'
Guest Page Deployment Pipeline
Three guest pages were modified and redeployed:
/tmp/quinn-guest.html— Created fresh/tmp/jonathan-afternoon.html— Created fresh/tmp/danika-guest.html— Updated 4 times (time corrections from 3-7pm paid, captain assignments)
Deployment Process:
- Downloaded existing templates from S3 bucket
sailjada-guest-pagesto preserve styling - Edited templates locally, injecting event-specific times, captain names, and waiver URLs
- Uploaded corrected pages to S3 at paths like
s3://sailjada-guest-pages/quinn-may-30.html - Invalidated CloudFront distribution cache (ID: likely tied to
d-guest.sailjada.com) to force edge nodes to fetch fresh copies
Why this approach? Storing guest pages in S3 + CloudFront means no server-side logic needed for rendering. The guest page is a static artifact, generated once during event setup. This eliminates latency concerns and simplifies disaster recovery—pages exist as immutable objects in versioned S3.
Invalidation command pattern:
aws cloudfront create-invalidation \
--distribution-id $DIST_ID \
--paths '/quinn-may-30.html' '/jonathan-afternoon.html' '/danika-guest.html'
DynamoDB Event Records: Crew Dispatch State Machine
Rather than trusting the calendar as the single source of truth at runtime, we created explicit event records in DynamoDB's crew-dispatch table with this schema:
{
"event_id": "quinn-may-30",
"event_name": "Quinn",
"date": "2026-05-30",
"time_window": "TBD (check guest page)",
"captain": "Quinn Male",
"crew": ["Darrell"],
"guest_count": 4,
"waiver_status": {
"quinn-primary": "SIGNED",
"quinn-minor": "PENDING_GUARDIAN"
},
"contact": {
"email": "quinn@example.com",
"phone": "+1-619-555-1234"
},
"photo_upload_code": "ABCD-1234-EFGH-5678",
"created_at": "2026-05-28T20:30:00Z",
"updated_at": "2026-05-28T20:35:00Z"
}
Why DynamoDB and not just the calendar?
- Decoupling: Calendar is marketing/scheduling; DynamoDB is operational. Crew captains can override crew assignments 2 hours before departure without modifying the calendar.
- Waiver tracking: Calendar doesn't store state. DynamoDB tracks which guests have signed waivers, whether minors need guardian countersignature, photo upload codes.
- Real-time dispatch: Lambda functions query DynamoDB at runtime (microseconds); parsing iCal and making Google API calls at dispatch time is too slow.
Waiver & Photo Upload Integration
The guest pages linked to a waiver endpoint (likely /waiver/index.html) that:
- Loaded an HTML form with event-specific details (date, time, captain)
- Checked Lambda logic for minor vs. adult flow (guardian signature required for minors)
- On submission, POST'd waiver data + file uploads to a Lambda function
- Lambda validated, stored in DynamoDB, and generated a unique photo upload code
We tested this end-to-end:
# Live test: POST minor waiver for Quinn with guardian data
curl -X POST 'https://waiver.sailjada.com/submit' \
-H 'Content-Type: application/json' \
-d '{
"event_id": "quinn-may-30",
"guest_name": "Quinn Jr.",
"age":