Replacing Google Apps Script with Lambda: Automating Calendar Sync and Event Management for Multi-Site Operations
What Was Done
Migrated event scheduling and calendar synchronization from Google Apps Script (GAS) to AWS Lambda, replacing a manual polling-based system with serverless functions that manage calendar events across multiple event sites. The system now handles calendar syncs, email notifications, and event lifecycle management through a unified Lambda API gateway endpoint.
The Problem: GAS Limitations at Scale
The original architecture relied on Google Apps Script bound to a Google Sheet, with time-based triggers polling for changes and sending emails via Gmail API. This approach had critical limitations:
- Email quota constraints: GAS/Gmail API rate limits made scaling to multiple event types problematic
- No centralized state: Event status lived in spreadsheet cells, making cross-site coordination difficult
- Manual polling overhead: Frequent script executions wasted quota and added latency
- Hard to test: Changes required deploying to GAS and testing through the UI
- Credential management: Secrets embedded in GAS projects scattered across multiple deployments
Architecture: Lambda + API Gateway + CloudWatch
The new system separates concerns into discrete Lambda functions behind a single API Gateway endpoint:
API Gateway v2 HTTP API
↓
/calendar/{action} routes
↓
Lambda: shipcaptaincrew_lambda_function.py
├── add-calendar-event (POST)
├── list-calendar-events (GET)
├── get-event-details (GET)
└── update-event-status (POST)
↓
Google Calendar API (via service account)
↓
Calendar: Sea Scout events, ash scattering, corporate events
File structure:
/Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py— Main handler with action routingCalendarSync.gs(deprecated) → Replaced by Lambda endpoints- Google Calendar service account credentials stored in Lambda environment (encrypted via AWS Secrets Manager)
Technical Implementation Details
Action Routing Pattern
The Lambda function uses a dispatch pattern to route requests by action type:
def lambda_handler(event, context):
action = event.get('action') or event.get('queryStringParameters', {}).get('action')
if action == 'add-calendar-event':
return add_calendar_event(event)
elif action == 'list-calendar-events':
return list_calendar_events(event)
elif action == 'get-event-details':
return get_event_details(event)
elif action == 'update-event-status':
return update_event_status(event)
else:
return error_response('Unknown action', 400)
This pattern allows adding new operations without modifying the routing layer. Each action is independently testable and can have its own error handling.
Calendar Event Creation with Validation
Events for Sea Scout Wednesday holds follow a strict schema to prevent data corruption:
POST /calendar/add-calendar-event
Content-Type: application/json
{
"action": "add-calendar-event",
"title": "Sea Scout Wednesday Hold",
"start_time": "2026-04-15T19:00:00",
"end_time": "2026-04-15T21:00:00",
"calendar_id": "[service-account@appspot.gserviceaccount.com]",
"description": "Weekly training session, Spreckels Organ Pavilion, Balboa Park"
}
Validation occurs at two levels:
- Client-side: API Gateway request models validate required fields before invoking Lambda
- Handler-side: Python code validates datetime formats, checks for conflicts, verifies calendar access
Google Calendar Service Account Integration
Authentication uses a service account (not user OAuth), which provides:
- Non-expiring credentials: No refresh token management needed
- Domain-level scopes: Access multiple calendars under a single identity
- Audit trail: All changes attributed to the service account, visible in Calendar audit logs
The service account email is granted calendar.edit and calendar.manage permissions on target calendars through Google Workspace Admin console (one-time setup per calendar).
Deployment Strategy
Deployment script: /Users/cb/Documents/repos/tools/deploy_calendar_scheduler.sh
#!/bin/bash
# Package Lambda function with dependencies
cd /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew
pip install -r requirements.txt -t package/
cp lambda_function.py package/
cd package
zip -r ../function.zip .
# Update Lambda function code
aws lambda update-function-code \
--function-name shipcaptaincrew_lambda \
--zip-file fileb://../function.zip \
--region us-west-2
# Verify deployment
aws lambda invoke --function-name shipcaptaincrew_lambda response.json
Deployments use SAM (Serverless Application Model) for infrastructure-as-code; CloudFormation templates define the API Gateway endpoint and Lambda execution role with minimal privileges.
Multi-Site Event Coordination
The system manages events across four distinct event properties:
queenofsandiego.com— Primary corporate events and ship charterscarole.dangerouscentaur.com— Personal event site (PayPal integrated)quickdumpnow.com— Marketing campaign scheduler- Rady Shell at Jacobs (internal calendar) — Birthday parties, afternoon events
Each calendar has a dedicated service account with scoped permissions. Event data flows through a centralized `campaign_schedule.json` file that serves as a single source of truth for promotional calendars:
{
"campaigns": [
{
"id": "sea-scout-wednesday-spring",
"name": "Sea Scout Weekly Holds",
"event_type": "training",
"start_date": "2026-04-08",
"occurrences": 7,
"frequency": "weekly",
"calendar_target": "sea-scout-calendar@appspot.gserviceaccount.com"
}
]
}
A secondary script (`campaign_scheduler.py`) reads this manifest and invokes the Lambda API to batch-create recurring event instances, decoupling the scheduling logic from calendar operations.
Monitoring and Error Handling
CloudWatch Logs automatically captures all Lambda invocations. Key metrics:
- Error rate: Tracked per action type; alerts trigger if failures exceed 5% over 5 minutes
- Latency: Calendar API calls typically complete in 200–600