```html

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:SendEmail and ses:SendRawEmail (for email delivery)
  • dynamodb:PutItem (for audit logging)
  • logs:CreateLogGroup, logs:CreateLogStream, logs:PutLogEvents (for CloudWatch)

Key Decisions and Trade-