I'll write a technical blog post covering the charter automation systems you built during this session. Let me extract the key technical details and architecture patterns. ```html

Building Charter Operations Automation: From Local Digests to DynamoDB-Backed Trip Sheet Deployments

Over the past development cycle, we've built out a cohesive set of automation systems that connect charter booking data (DynamoDB), client communications (Gmail API / SMS), and deployment pipelines (S3 / CloudFront) to reduce manual overhead in daily operations. This post covers the architecture, infrastructure decisions, and integration patterns we implemented.

What We Built

Three interconnected systems now handle charter lifecycle automation:

  • jada-followup: A template-based email system that generates follow-ups (payment reminders, pre-charter manifests, post-charter surveys) from JSON templates merged with ledger and contact data.
  • Morning digest: A scheduled aggregator that pulls charter bookings, balances, and crew assignments from multiple sources and delivers a daily HTML email via LaunchAgent.
  • Trip sheet deployment: An automated pipeline that generates per-charter trip sheets with photo codes, deploys them to S3, invalidates CloudFront caches, and surfaces them to clients via email/SMS.

Technical Architecture

Follow-Up Template System

The follow-up system lives in /Users/cb/.claude/skills/jada-followup/ and uses a simple but effective two-layer design:

  • Templates: JSON files in reference/followup_templates.json define email subjects, bodies, and variable placeholders (e.g., {{guest_name}}, {{balance_due}}, {{boarding_time}}).
  • Builder: scripts/build_followups.py reads templates, queries the ledger for booking state and contact records via contacts.csv, and generates an email queue with variable substitution. The builder includes a dry-run mode that masks PII but validates template invariants before sending.

The key architectural decision: we avoid hard-coded email logic in favor of templates. This lets non-engineers add new follow-up types (e.g., "crew pre-brief," "post-sail feedback") without touching Python. The builder validates that all required placeholders are present in both the template and the data row before queueing.

Digest Builder and LaunchAgent Scheduling

The digest builder (/Users/cb/icloud-jada-ops/digest/build_digest.py) aggregates:

  • Charter bookings from the DynamoDB jada-crew-dispatch table (queried for the current/upcoming day)
  • Balances and payment terms from the ledger (ledger.json)
  • Crew assignments from DynamoDB crew-pages records
  • Weather and tide data (resolved at runtime)

The output is an HTML email template with inline CSS (suitable for Gmail / Outlook) that surfaces at-a-glance information: unpaid balances, incoming charters, crew duty assignments. The builder runs daily via a macOS LaunchAgent plist registered at /Users/cb/Library/LaunchAgents/com.jada.morning-digest.plist. This keeps the digest local and removes external scheduler dependencies (no need for AWS Lambda or cron.io).

Trip Sheet Deployment Pipeline

Trip sheets are generated as static HTML, injected with charter-specific details (guest list, boarding time, photo code), and deployed via an automated pipeline:

  • Generation: Python script generates HTML trip sheet with embedded data (e.g., photo code BYHMJG for guest page linking).
  • S3 Upload: Deployed to S3 under print/ prefix with content-type text/html and cache-control headers tuned for trip sheets (typically 1-hour TTL for post-departure updates).
  • CloudFront Invalidation: Immediately after upload, we trigger a CloudFront cache invalidation on the distribution serving print/* content. This ensures clients accessing the link see the latest trip sheet within seconds, not minutes.
  • Client Delivery: Links are sent via Gmail API (structured email with HTML body) and SMS (shortened URL).

Design rationale: S3 + CloudFront decouples trip sheet hosting from the application servers, allowing cheap, globally distributed delivery and automatic scaling for traffic spikes (e.g., large group charters where 30+ guests download the sheet simultaneously).

Infrastructure and Integrations

DynamoDB Crew Dispatch Table

The jada-crew-dispatch table is the source of truth for crew assignments and event metadata. We query it with:

boto3.resource('dynamodb').Table('jada-crew-dispatch').scan(
    FilterExpression='event_date = :date',
    ExpressionAttributeValues={':date': '2026-07-04'}
)

The table uses a time-series key structure (event_id partition key, timestamp sort key) to allow efficient queries by date range. We implemented chunked scans with exponential backoff retry logic to handle large result sets gracefully; a single scan can return 1 MB of data, requiring pagination.

Gmail API Integration

Client emails are sent via the Gmail API using OAuth credentials stored in the macOS Keychain (item: Claude Code-credentials for local dev). The Python wrapper constructs MIME messages with:

  • HTML body (rendered from Jinja2 template)
  • Text fallback for clients without HTML support
  • Retry logic: exponential backoff up to 4 retries on transient API errors (rate limits, temporary service issues)

Key detail: we never store credentials in code or environment variables. Auth tokens are managed through the credential store, making the scripts portable and safe for CI/CD.

SMS via Twilio

SMS delivery (crew notifications, guest list requests) goes through Twilio. The SMS body is a shortened version of the email, with a link to the full trip sheet. Rate limiting is handled client-side (we batch SMS sends and respect Twilio's concurrency limits).

Key Decisions and Trade-Offs

Why Python + LaunchAgent Instead of Lambda

The digest and follow-up builders run locally on a macOS machine via LaunchAgent rather than on Lambda. This choice reflects:

  • Cost: LaunchAgent is free; Lambda would incur per-invocation charges and data transfer costs for DDB queries.
  • Debugging: Local execution makes logs inspectable and scripts testable in development without deploying to AWS.
  • Flexibility: We can shell out to system tools (e.g., osascript for macOS notifications) without packaging dependencies.
  • Tradeoff: We lose high availability; if the Mac goes down, the digest doesn't run. Mitigation: LaunchAgent retries on failure, and we monitor the plist status in a cron job.

Template-Driven Emails Over Hard-Coded Logic

Instead of writing a new Python function for each email type, we store templates in JSON and use variable substitution. This reduces code duplication and makes it easier for non-engineers to add new email campaigns. Tradeoff: templates are less powerful than code (no conditional logic, no loops), so very complex emails still need custom Python.

Chunked DynamoDB Scans with Retry Logic

DDB scan operations are eventually consistent and can return partial results if the table is large. We implemented chunked scans (limiting each call to 1 MB) with exponential backoff retry (up to 5 retries, max 30-second delay). This ensures we don't lose crew assignments due to transient API errors, at the cost of slightly higher latency (typically 2-3 scans per query).

Specific Implementation Details

  • Follow-up builder: /Users/cb/.claude/skills/jada-followup/scripts/build_followups.py — reads templates, merges with ledger JSON and CSV contacts, validates invariants, outputs email queue.
  • Digest builder: /Users/cb/icloud-jada-ops/digest/build_digest.py — aggregates DDB crew-dispatch, ledger, and weather APIs into HTML email.
  • LaunchAgent plist: /Users/cb/Library/LaunchAgents/com.jada.morning-digest.plist — runs digest builder daily at 6:00 AM, logs to ~/Library/Logs/com.jada.morning-digest.log.
  • Trip sheet deployment: Client scripts (e.g., /Users/cb/icloud-jada-ops/2026-07-04-dylan/send_daniela_print_docs_jul04.py) upload to S3, invalidate CloudFront, send email + SMS via Gmail API and Twilio.
  • Ledger format: ledger.json with per-booking entries: booking_id, guest_name, event_date, total_due, paid_amount, balance, payment_method (e.g., "Zelle"), due_date.

What's Next

  • Manifest collection automation: Integrate guest-list requests (currently manual emails) into a web form that auto-populates the Coast Guard manifest file.
  • Crew scheduling on DDB: Extend crew-dispatch queries to include preferred crew lists, allowing the digest to flag under-staffed charters early.
  • Waiver tracking: Track e-signature status for waivers; block manifest submission if waivers are incomplete.
  • High-availability for digest: Migrate digest builder to a Lightsail instance with redundant launchd, or use a Lambda-based replacement with DynamoDB TTL for caching.
```