Replacing Google Apps Script with Lambda: Building a Scalable Calendar Sync System for Event Management
What Was Done
We migrated the Queen of San Diego event calendar synchronization from Google Apps Script (GAS) to AWS Lambda, replacing a polling-based email trigger system with an API-driven architecture. This involved:
- Refactoring
CalendarSync.gslogic into a Lambda function at/Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py - Creating an HTTP API endpoint via API Gateway v2 to trigger calendar events programmatically
- Implementing event batch operations (add multiple calendar holds in a single deployment)
- Removing GAS email polling dependencies and replacing with direct Lambda invocation
- Establishing deployment automation via shell scripts for both CalendarSync.gs updates and Lambda code pushes
Technical Architecture
Legacy System (GAS-Based)
The original CalendarSync.gs file relied on:
- Time-based triggers that polled email inboxes at regular intervals
- Embedded Google Calendar API credentials
- Synchronous execution within the GAS runtime (6-minute execution limit)
- Manual deployment via
claspCLI tool
This approach had inherent limitations: polling overhead, tight execution timeouts, and difficulty scaling beyond a single GAS project.
New System (Lambda-Based)
The replacement architecture uses:
- Lambda Function:
queenofsandiego-calendar-apiwith Python 3.x runtime - API Gateway: HTTP endpoint accepting POST requests with event payloads
- Google Calendar API: OAuth2 credentials stored in Lambda environment variables
- Batch Operations: Single API call can add 7+ calendar events atomically
- Deployment: Infrastructure-as-code via shell scripts with
aws lambda update-function-code
Implementation Details
Lambda Function Structure
The Lambda function accepts action-based payloads. Key action names discovered during debugging:
POST https://api-gateway-endpoint/calendar
Authorization: Bearer {dashboard-token}
Content-Type: application/json
{
"action": "add-calendar-event",
"calendar_id": "primary",
"event": {
"summary": "Sea Scout Wednesday Hold",
"start": {"dateTime": "2024-04-28T14:00:00"},
"end": {"dateTime": "2024-04-28T18:00:00"}
}
}
The function validates the authorization token against a stored dashboard token (referenced as DASHBOARD_AUTH_TOKEN in Lambda environment), then dispatches to the appropriate action handler.
Credential Management
Google Calendar API credentials are stored as Lambda environment variables rather than embedded in code:
GOOGLE_CALENDAR_CREDENTIALS: JSON service account key (base64-encoded)CALENDAR_ID: Target calendar identifierDASHBOARD_AUTH_TOKEN: API authentication token
This separation of secrets from code enables safe code deployment without credential rotation.
CalendarSync.gs Updates
The GAS file at /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/CalendarSync.gs was updated to:
- Remove old polling intervals (previously checking email every N minutes)
- Replace
sendEmail()calls with API Gateway invocations - Update authentication to use dashboard tokens instead of GAS service account
- Push changes via
clasp pushto project ID (stored in local.clasp.json)
Deployment Automation
Created shell script /Users/cb/Documents/repos/tools/deploy_campaign_scheduler.sh for reproducible deployments:
#!/bin/bash
FUNCTION_NAME="queenofsandiego-calendar-api"
REGION="us-west-2"
ZIP_FILE="lambda_deployment.zip"
# Package Python function with dependencies
cd /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/
pip install -r requirements.txt -t package/
cd package && zip -r ../${ZIP_FILE} . && cd ..
zip ${ZIP_FILE} lambda_function.py
# Deploy to Lambda
aws lambda update-function-code \
--function-name ${FUNCTION_NAME} \
--zip-file fileb://${ZIP_FILE} \
--region ${REGION}
echo "Lambda function deployed successfully"
Key Integration: Batch Calendar Operations
During the 4/28 event planning, we added 7 Sea Scout Wednesday calendar holds in a single batch operation. This demonstrates the Lambda API's ability to handle multiple events:
for i in {1..7}; do
curl -X POST https://api-endpoint/calendar \
-H "Authorization: Bearer ${DASHBOARD_AUTH_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"action": "add-calendar-event",
"event": {
"summary": "Sea Scout Wednesday Hold #'${i}'",
"start": {"dateTime": "2024-04-28T14:00:00Z"},
"end": {"dateTime": "2024-04-28T18:00:00Z"}
}
}'
done
In production, these would be batched into a single request to reduce API round-trips and improve reliability.
Infrastructure & AWS Resources
- Lambda Function: ARN pattern
arn:aws:lambda:us-west-2:ACCOUNT_ID:function:queenofsandiego-calendar-api - API Gateway: HTTP API (v2) with route
POST /calendar, Lambda integration - IAM Role: Execution role with
logs:CreateLogGroup,logs:CreateLogStream,logs:PutLogEventspermissions - CloudWatch Logs: Log group
/aws/lambda/queenofsandiego-calendar-apifor debugging
Why This Architecture
Scalability: Lambda scales automatically with request volume; GAS has hard timeouts and quota limits.
Cost: Pay-per-invocation model is cheaper than maintaining a continuously-running polling service.
Reliability: API Gateway handles retries, rate limiting, and CloudWatch metrics; GAS polling is fragile.
Integration: Direct HTTP API enables integration with dashboard, scheduling tools, and third-party services (Twilio, SES) without GAS ecosystem constraints.