Building a Multi-Tenant Event Management System: Replacing Google Apps Script with Serverless Architecture
What Was Done
We migrated event management and calendar synchronization from Google Apps Script (GAS) to a hybrid serverless architecture combining AWS Lambda, API Gateway, and direct Google Calendar API integration. This involved:
- Replacing email-polling triggers with on-demand Lambda invocations
- Implementing a centralized calendar sync system handling multiple calendar sources (Google Calendar, iCal feeds, platform integrations)
- Building an API layer for authenticated event operations across multiple domains
- Creating a boat cleaning dispatch system with platform scraping and task automation
- Deploying credential management through environment variables rather than inline GAS secrets
Technical Architecture
Calendar Synchronization System
The core system lives in /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/CalendarSync.gs. This GAS file handles:
- Email polling: Checking Gmail inboxes for calendar event invitations and platform notifications
- iCal parsing: Processing GetMyBoat and Boatsetter calendar feeds
- Google Calendar API calls: Creating, updating, and deleting events via direct API (not GAS Calendar service)
- Multi-tenant handling: Managing events across carole.dangerouscentaur.com, queenofsandiego.com, and operational calendars
The file uses Google Apps Script's native email and calendar APIs, but all actual calendar mutations go through the Google Calendar API REST endpoints with OAuth service account credentials stored in repos.env.
Lambda-Based Event API
A Lambda function (exact name found via AWS CLI querying) exposes calendar operations through API Gateway. The endpoint structure:
POST /api/calendar/{action}
Headers: Authorization: Bearer {token}
Body: JSON event object
Supported actions include:
add-calendar-event— Create single or batch eventslist-calendar-events— Query events within date rangesupdate-calendar-event— Modify existing eventsdelete-calendar-event— Remove events
Authentication uses a static bearer token stored in repos.env and validated by Lambda authorizer middleware.
Boat Cleaning Dispatch System
Two new tools were created in /Users/cb/Documents/repos/tools/:
- dispatch_boat_cleaner.py — Scrapes boat platform listings (GetMyBoat, Boatsetter), extracts cleaning requirements, creates dispatch tasks
- deploy_inbox_scraper.sh — Orchestrates deployment of platform_inbox_scraper.py to compute environment
- platform_inbox_scraper.py — Monitors email inboxes for platform notifications, triggers dispatch on new booking events
The dispatch flow:
Platform Notification Email
↓
Inbox Scraper (runs hourly via cron or Lambda scheduled event)
↓
Extract Booking Details (dates, boat, guest count)
↓
dispatch_boat_cleaner.py (creates task in dashboard or CRM)
↓
Assign to service provider
↓
Webhook callback updates calendar hold
Static Site Deployments
Dashboard Updates
Modified /tmp/dashboard_index.html with:
- Photo auto-send functionality for user uploads
- Task card state management for Scout calendar holds, ash scattering events, boat cleaning
- Real-time sync with Lambda calendar API for event verification
Deployed via S3 bucket sync with CloudFront cache invalidation (specific distribution ID stored in infrastructure docs).
Carole Event Site
Updated carole.dangerouscentaur.com hosted in /Users/cb/Documents/repos/sites/queenofsandiego.com/:
- Removed burial-at-sea event content (sensitive/outdated)
- Updated PayPal donation link to current endpoint
- Integrated calendar feed from Google Calendar API (public, filtered events only)
- Deployed via git push to GitHub, triggering webhook build in CI/CD
Key Architectural Decisions
Why Lambda for Calendar API Instead of Direct GAS
GAS has inherent limitations for multi-tenant operations:
- Email polling uses time-based triggers (minimum 1-minute frequency), causing scalability issues with high notification volume
- No native rate-limiting or authentication layer
- Credentials embedded in script (security anti-pattern)
Lambda provides:
- Invocation via HTTP API with token-based auth
- Native credential injection via environment variables
- Scalable concurrent execution
- Audit logging via CloudWatch
Hybrid GAS + Lambda Approach
We kept GAS for email polling (it's still the cleanest way to read Gmail) but delegated all Google Calendar mutations to Lambda. This way:
- GAS handles asynchronous event discovery (emails are idempotent input)
- Lambda handles deterministic state changes (calendar events)
- Separation of concerns prevents GAS quota exhaustion from blocking calendar writes
Environment Variable Management
All credentials (Google OAuth keys, API tokens, S3 access keys) moved from inline GAS to repos.env, sourced at runtime by deployment scripts. Benefits:
- Credentials never appear in version control
- Rotation doesn't require code redeploy
- Per-environment secrets (dev vs. prod) handled via .env file variants
Integration Points
The system now connects:
- Gmail API — CalendarSync.gs polls inboxes for booking/event emails
- Google Calendar API — Lambda creates/manages events via REST (not GAS Calendar service)
- GetMyBoat / Boatsetter iCal feeds — Parsed by dispatch_boat_cleaner.py
- Dashboard web app — Calls Lambda calendar API for real-time sync and verification
- SES — Confirmation emails sent to stakeholders (Carole, CB, service providers)
- CloudFront / S3 — Static sites (dashboard, carole event site) invalidated on content updates
What's Next
- Webhook callbacks: Service providers will confirm cleaning completion via webhook, automatically updating calendar holds
- Platform API polling: Extend platform