```html

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.gs logic 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 clasp CLI 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-api with 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 identifier
  • DASHBOARD_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 push to 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:PutLogEvents permissions
  • CloudWatch Logs: Log group /aws/lambda/queenofsandiego-calendar-api for 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.

What's Next