Automating Multi-Site Event Scheduling: Lambda APIs, Google Apps Script, and Calendar Synchronization
This post covers a recent infrastructure refactor that unified event scheduling across multiple web properties by replacing manual Google Apps Script deployments with Lambda-backed APIs and implementing robust calendar synchronization patterns.
The Problem: Fragmented Scheduling Systems
The Queen of San Diego ecosystem operates multiple distinct web properties—the main site, event microsites (birthday events, Scout activities), and partner integrations—each with its own event management needs. Previously, calendar operations relied on:
- Google Apps Script (GAS) projects deployed manually across multiple properties
- Direct Google Calendar API calls with inconsistent authentication patterns
- Email-based communication workflows for event coordination
- Manual calendar event creation with no audit trail or scheduling consistency
The challenge: adding bulk events (like seven recurring Sea Scout Wednesday holds) required either manual UI interaction or writing one-off scripts. There was no unified API layer for event management across properties.
Architecture: Lambda API as Calendar Abstraction Layer
The solution implemented a Lambda function at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py that serves as a centralized calendar control plane. This function:
- Accepts authenticated API calls via API Gateway v2
- Dispatches actions (create event, list events, update event, sync calendar)
- Manages Google Calendar API credentials stored in Lambda environment variables
- Returns structured JSON responses for programmatic consumption
The Lambda function signature uses an action-based dispatcher pattern:
def lambda_handler(event, context):
action = event.get('action')
if action == 'add-calendar-event':
return handle_add_event(event)
elif action == 'list-events':
return handle_list_events(event)
elif action == 'sync-calendar':
return handle_sync_calendar(event)
# ... additional actions
This design allows new calendar operations to be added by simply extending the dispatcher without modifying API Gateway routes or client code.
Integration Point: Google Apps Script CalendarSync
Parallel to the Lambda API work, the Google Apps Script project at /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/CalendarSync.gs was refactored to:
- Call the Lambda API endpoint instead of directly invoking Google Calendar API
- Handle batch event operations (the seven Scout holds scenario)
- Implement exponential backoff retry logic for transient failures
- Log all operations to Google Sheets for audit purposes
The key change was replacing direct Calendar API calls:
// Old pattern (direct API)
CalendarApp.getDefaultCalendar().createEvent(title, startTime, endTime);
// New pattern (via Lambda)
const payload = {
action: 'add-calendar-event',
calendarId: 'primary',
event: {
summary: title,
start: { dateTime: startTime },
end: { dateTime: endTime }
}
};
const response = callLambdaAPI(payload, authToken);
This indirection provides several benefits: centralized credential management, consistent error handling, and the ability to log/audit all calendar operations through a single point.
Batch Event Creation: Seven Scout Holds
A concrete example demonstrates the system in practice. Adding seven recurring Wednesday holds for Sea Scout activities required:
- Constructing an array of event objects with proper timezone handling (all events in Pacific time, recurring weekly)
- Iterating the array and calling the Lambda API's
add-calendar-eventaction for each event - Collecting responses and detecting partial failures
- Returning a summary showing which events succeeded and which failed
The payload structure for a single Scout hold:
{
"action": "add-calendar-event",
"calendarId": "primary",
"event": {
"summary": "Sea Scout Wednesday Hold",
"start": {
"dateTime": "2025-05-07T18:00:00",
"timeZone": "America/Los_Angeles"
},
"end": {
"dateTime": "2025-05-07T20:00:00",
"timeZone": "America/Los_Angeles"
},
"recurrence": ["RRULE:FREQ=WEEKLY;BYDAY=WE"]
}
}
Deployment: CloudFormation and Manual GAS Push
The infrastructure uses two deployment mechanisms:
- Lambda: Deployed via AWS CloudFormation with environment variables for Google API credentials. The function is integrated with API Gateway v2, providing HTTPS endpoints at stable URLs.
- Google Apps Script: Pushed via the
clasptool from the GAS project directory. The.clasp.jsonfile maps the local directory to the GAS project ID, enabling version-controlled deployments.
The clasp push command deploys code changes immediately, but the deployment itself is still manual—it's triggered as part of the session workflow rather than through CI/CD pipelines. This was a deliberate choice to maintain control over critical calendar-touching code.
Key Decision: Lambda as Abstraction vs. Direct Client Calls
One might ask: why introduce Lambda when Google Apps Script can call the Calendar API directly?
- Credential Management: Secrets stored in Lambda environment variables rather than embedded in GAS code or environment configs. Rotation happens in one place.
- Audit Trail: All calendar operations flow through a single function that can log to CloudWatch, making it easier to troubleshoot calendar synchronization issues.
- Rate Limiting: Lambda can implement backoff and queue management, protecting against Google Calendar API quota exhaustion.
- Multi-Calendar Support: The API can target different calendars (personal, team, venue calendars) by parameterizing the
calendarIdfield. - Testing: Calendar logic can be tested locally by mocking the Lambda response, decoupling GAS testing from Google's APIs.
Next Steps: Event Synchronization and Boat Platform Integration
Planned improvements include:
- Implementing a
sync-calendaraction that pulls events from third-party booking platforms (GetMyBoat, Boatsetter) and creates corresponding calendar entries. - Adding webhook handlers to respond to calendar changes and update booking platforms in near-real-time.
- Building a dashboard card that displays calendar conflicts and scheduling gaps, powered by the Lambda API.
- Extending the pattern to handle email notifications—triggering SES emails when calendar events are created or modified.
The architecture is now positioned to handle complex, multi-system scheduling workflows while maintaining a clean separation between presentation (web UIs and GAS), logic (Lambda), and external services (Google Calendar, booking platforms, email systems).
```