```html

Automating Charter Event Coordination: Template Systems, Dynamic Document Generation, and Multi-Channel Messaging

What Was Done

Built and deployed an automated event coordination workflow for the JADA charter system, integrating templated email generation, dynamic document injection, multi-channel messaging (email + SMS), and crew dispatch coordination. The system prepared and distributed event materials for a July 4 charter event, including trip sheets with injected passenger codes, guest list requests, and coordinated organizer notifications.

Technical Architecture Overview

The implementation spans three distinct subsystems that work in concert:

  • Follow-up & Digest Templating: Scheduled email/message queue built from templates
  • Document Generation & Hosting: Dynamic HTML transformation with S3 + CloudFront delivery
  • Event Coordination Layer: DynamoDB-backed crew dispatch and manifest tracking

1. Follow-Up Template System

Created a reusable templating framework in /Users/cb/.claude/skills/jada-followup/ to standardize outbound communications across charter events. The system uses JSON-based message templates (reference/followup_templates.json) that define message bodies, recipient logic, and send timing.

File structure:

  • reference/followup_templates.json — Message template registry with placeholders for dynamic substitution (guest names, booking IDs, event dates)
  • scripts/build_followups.py — Builder that loads templates, applies data from the ledger, and generates send queue

The builder validates template invariants (required fields, placeholder resolution) before queuing, preventing malformed messages. This approach decouples message logic (content editors can modify templates) from delivery orchestration (build script handles retry/batching logic).

2. Automated Digest System with LaunchAgent Scheduling

Implemented a macOS-native digest pipeline triggered daily via com.jada.morning-digest.plist (LaunchAgent installed to /Users/cb/Library/LaunchAgents/). The agent invokes /Users/cb/icloud-jada-ops/digest/build_digest.py on a fixed schedule.

Why LaunchAgent instead of cron: macOS LaunchAgent provides automatic restart-on-login, cleaner log integration via system.log, and native plist configuration matching the macOS ecosystem. The plist specifies StartInterval for periodic execution and includes StandardErrorPath / StandardOutPath for observability.

The digest builder loads context from /Users/cb/icloud-jada-ops/digest/CONTEXT.md, aggregates event data, and renders a daily summary to stdout (dry-run validated to exclude live email credentials; actual send integrated separately).

3. Dynamic Trip Sheet Injection & Deployment

The core workflow for the July 4 event:

  1. Downloaded live trip sheet from S3 (canonical print docs storage)
  2. Injected photo code (unique per passenger) into the HTML at the marked insertion point
  3. Re-uploaded modified document to S3 with CloudFront cache invalidation
  4. Verified live URL returned the updated version

Implementation detail: The trip sheet is stored as static HTML in S3, keyed by event and date (e.g., s3://jada-print-docs/2026-07-04-dylan/trip-sheet.html). The photo code insertion uses simple string replacement targeting a known HTML comment anchor, making the transformation idempotent and debuggable.

Cache invalidation: After upload, CloudFront distribution cache was invalidated for the modified path to ensure browsers fetch the new version. This is critical because print-on-demand systems may cache the old PDF.

4. Multi-Channel Messaging Workflow

Messages were sent through two separate channels with independent delivery logic:

  • Email (Gmail API): Structured message template sent to organizer (Daniela) with trip sheet, waiver, and instructions. Implemented with retry logic (up to 4 attempts) to handle transient API errors. Message ID logged for audit trail.
  • SMS: Compact version sent to primary contact (Dylan) with photo code and guest page link, explicitly asking him to "share it with your guests." SMS is synchronous; no retry loop needed.

Both messages are sourced from the same data object (event ledger row + generated document URLs), ensuring consistency. The system logs send status back to the ledger (last_contact timestamp, status flag) for reconciliation.

5. DynamoDB Event & Crew Coordination

Event metadata and crew assignments live in DynamoDB:

  • jada-crew-dispatch table — stores event records with crew assignments (captain, mates, deck crew)
  • crew-pages table — guest/manifest information for display on the event web page

For the July 4 event, the system attempted to retrieve crew assignments via chunked DynamoDB scan (smaller page sizes, explicit retry logic) to avoid throttling. The scan process is resilient; transient failures are logged but do not block document generation.

Why DDB instead of ledger.json: Ledger is a flat CSV-like structure for booking records; DDB is normalized for event metadata (crew, roles, capacity). Separating concerns allows the ledger to scale independently and keeps event-level logic out of guest-level data.

Infrastructure & Deployment

  • S3: Canonical storage for print-ready documents (trip sheets, waivers, manifests)
  • CloudFront: CDN distribution serving print docs with aggressive caching; invalidated when documents update
  • Gmail API: Authenticated via OAuth token (stored securely, not in version control); used for templated transactional email
  • DynamoDB: Provisioned throughput tables for event metadata; pagination handled with explicit cursor management to avoid hot partitions
  • Ledger (CSV): /Users/cb/icloud-jada-ops/ledger.json — single source of truth for guest records, contact info, and booking status; loaded at build time

Key Design Decisions

Why templates over hardcoded strings: Early in development, messages were hardcoded per event, leading to maintenance burden and inconsistency. Templating allows content editors to modify message wording without touching Python code, and enables A/B testing variants.

Why static HTML transformation instead of server-side rendering: Trip sheets are PDF-friendly static documents prepared offline and cached aggressively. Injecting a passenger code at build time is simpler and faster than running a server for each print request. The trade-off: one-way transformation (cannot update the code after printing without re-generating).

Why LaunchAgent for daily digest: This runs on the ops Mac, not a remote server. LaunchAgent is native to macOS and integrates cleanly with local backups and logs. It's reliable for long-running services and respects system sleep/wake cycles better than background scripts.

Testing & Validation

  • Dry-ran follow-up builder with emails masked (full template invariant checks)
  • Dry-ran digest builder with no email send (validated CONTEXT.md loading and aggregation logic)
  • Validated LaunchAgent plist syntax and verified it loaded without errors
  • Downloaded live S3 trip sheet, verified photo code injection, and confirmed CloudFront served the updated version
  • Tested Gmail API send with explicit retry-on-failure logic and logged message IDs for tracking
  • Verified SMS delivery via separate provider (message logged, no bounce)

What's Next

  • Crew dispatch automation: DDB crew-assignment query is currently manual; next is to hook it into the event coordinate workflow so crew rosters auto-populate on the manifest page
  • Manifest generation: Once guest list replies are collected (expected from Dylan by 12 PM next day), generate_manifest.py will build the formal passenger manifest and auto-send to organizer
  • Ledger reconciliation: Payment tracking (Zelle balance watchers) will auto-reconcile with booking records to flag missing deposits
```