Automating Event Calendar Synchronization and Dispatch Operations Across Multiple Platforms
This session focused on consolidating calendar management, automating service dispatch workflows, and integrating multiple event platforms into a unified operational system. The work involved refactoring Google Apps Script infrastructure, building Python-based automation tools, and establishing reliable data flow between third-party booking platforms and internal systems.
What Was Done
- Migrated calendar synchronization logic from Apps Script to a Lambda-based API architecture
- Created dispatch automation for service scheduling (boat cleaning operations)
- Built email campaign scheduling system with templated messaging
- Established credential management and platform integration patterns
- Debugged and optimized calendar event ingestion from multiple sources
Technical Details: Calendar Synchronization Architecture
The core challenge was moving away from tightly-coupled Apps Script polling towards a more scalable Lambda-based event system. The previous architecture relied on CalendarSync.gs executing time-driven triggers that would poll external calendar sources at fixed intervals.
File: /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/CalendarSync.gs
This script was refactored multiple times during the session to:
- Remove hardcoded email polling intervals (reducing from 5-minute checks to event-driven calls)
- Implement credential injection via environment variables rather than embedded secrets
- Abstract platform-specific calendar operations into a pluggable action system
The action-based dispatch pattern in the Lambda function supports operations like:
GET /calendar/events — Retrieve all calendar events
POST /calendar/add-event — Insert single event with validation
POST /calendar/sync-platform — Bulk synchronization from external source
DELETE /calendar/event/{id} — Remove event by ID
Why this approach? Direct Lambda invocation provides:
- Lower latency than Apps Script time-driven triggers (50-200ms vs 30s+ cold starts)
- Fine-grained control over concurrency and error handling
- Infrastructure-as-code compatibility (Lambda functions versioned in git)
- Testability — functions can be invoked locally or via API Gateway
Dispatch Automation: Boat Cleaning Service Integration
Created two new tools to manage recurring service dispatch:
File: /Users/cb/Documents/repos/tools/dispatch_boat_cleaner.py
This script pulls booking data from GetMyBoat/Boatsetter APIs and generates work orders:
#!/usr/bin/env python3
# Reads vessel availability calendar
# Checks for boats marked "cleaning needed"
# Dispatches notifications to service team
# Logs completion status to DynamoDB table: boat-service-logs
Why separate this from the calendar sync? Service dispatch has different SLAs and error handling:
- Calendar sync is idempotent (safe to retry); dispatch is at-least-once (must track state)
- Dispatch failures should trigger alerts; calendar mismatches should retry silently
- Different credential sets (service team Slack vs. calendar API keys)
Email Campaign Infrastructure
Built a templated campaign scheduling system for multi-recipient messaging:
Files:
/Users/cb/Documents/repos/tools/campaign_scheduler.py/Users/cb/Documents/repos/tools/campaign_schedule.json/Users/cb/Documents/repos/tools/deploy_campaign_scheduler.sh
The system uses a JSON configuration file to define send schedules:
{
"campaigns": [
{
"id": "rady_shell_blast_1",
"template": "rady_shell_blast1.html",
"recipients": ["contact_list_id"],
"send_time": "2024-04-28T09:00:00Z",
"retry_attempts": 3,
"service": "SES"
}
]
}
Templates stored in /Users/cb/Documents/repos/tools/templates/ use Jinja2 variable substitution for personalization. Why this separation of concerns?
- Auditability — JSON config is human-readable and version-controllable
- Decoupling — template designers don't need to modify Python code
- Scheduling flexibility — campaigns can be paused, resumed, or rescheduled without code changes
- Instrumentation — send metrics logged to CloudWatch for delivery tracking
Multi-Site Deployment Pattern
The session involved managing tools across three distinct site codebases:
/Users/cb/Documents/repos/sites/queenofsandiego.com//Users/cb/Documents/repos/sites/quickdumpnow.com//Users/cb/Documents/repos/sites/carole.dangerouscentaur.com/
Each site has its own consumer blast tool (e.g., qdn_consumer_blast.py) and analytics script (e.g., qdn_stats.py). While these are similar, maintaining site-specific versions allows:
- Independent rate limiting (quickdumpnow can have different SES limits than Rady Shell)
- Custom templates (each site's branding, compliance requirements)
- Isolated credentials (different AWS IAM principals per site)
- Rollback isolation (bugs in one site's tool don't impact others)
However, common logic is shared via imports from /Users/cb/Documents/repos/tools/, reducing duplication.
Credential Management Strategy
Credentials are loaded from repos.env using a pattern:
import os
from dotenv import load_dotenv
load_dotenv('/Users/cb/Documents/repos/repos.env')
calendar_api_key = os.getenv('GAS_CALENDAR_API_TOKEN')
ses_role_arn = os.getenv('AWS_SES_ROLE_ARN')
This allows the same code to run locally (dev mode, using local env file) or in Lambda (using IAM role credentials). The Lambda execution role has permissions for:
ses:SendEmailandses:SendRawEmail(for email delivery)dynamodb:PutItem(for audit logging)logs:CreateLogGroup,logs:CreateLogStream,logs:PutLogEvents(for CloudWatch)