```html

Multi-Domain Charter Event Pipeline: Coordinating Guest Pages, Crew Notifications, and Calendar Sync Across S3, CloudFront, and Lambda

What We Built

This session implemented an end-to-end charter booking workflow that spans three distinct technical domains:

  • A guest-facing booking confirmation page deployed to queenofsandiego.com via S3 + CloudFront with path rewriting
  • Automated crew notifications through ShipCaptainCrew (SCC) event creation with magic link authentication
  • JADA Internal Calendar entries synchronized with the booking system
  • Direct S3 API bypasses for CloudFront header stripping issues

Technical Architecture

Guest Page Deployment Strategy

The guest page was initially created as /tmp/jada-guest-xhqgmdh.html and uploaded to the sailjada.com S3 bucket. However, a critical decision emerged: the page needed to live on queenofsandiego.com instead, with a friendly URL pattern at /g/.

The queenofsandiego.com CloudFront distribution uses a custom origin behavior function (deployed via Lambda@Edge) that rewrites paths. The function follows this convention:


Request: /g/[BOOKING_ID].html
Origin path rewrite: /guests/[BOOKING_ID].html
S3 bucket: queenofsandiego.com

This pattern allows clean guest URLs while maintaining flat HTML file storage in S3. The file was uploaded as guests/XHQGMDH.html (flat structure, not nested directories) to respect the CF function's expectations. CloudFront cache was invalidated immediately using the distribution ID for queenofsandiego.com.

Authentication Challenges: CloudFront Header Stripping

The ShipCaptainCrew API requires service key authentication via Authorization: Bearer {SERVICE_KEY} headers. When routing through CloudFront (which has a managed cache policy), these custom headers are stripped before reaching the origin Lambda.

Solution: Hit the API Gateway endpoint directly, bypassing CloudFront entirely. The SCC Lambda is fronted by API Gateway, which does not strip custom headers. This required:

  • Locating the API Gateway invocation URL (not the CloudFront distribution URL)
  • Verifying the SCC Lambda environment variable SERVICE_KEY_HASH was set correctly
  • Confirming the Lambda's hash_password function was computing bcrypt hashes to match the stored hash

Event Creation & Crew Notification

SCC event creation triggers automatic crew notifications with magic links. The event payload includes:


POST /events (to SCC API Gateway, not CloudFront)
Body: {
  "eventName": "[Charter name]",
  "eventDate": "YYYY-MM-DD",
  "eventTime": "HH:MM",
  "notes": "[Crew-facing details]",
  "crewIds": ["[crew-member-id]", ...]
}
Header: Authorization: Bearer {SERVICE_KEY}

The Lambda handler (in /tmp/scc-lambda-src/lambda_function.py) validates the service key hash, generates a DynamoDB record, and invokes SNS to notify crew members with a magic link that includes the event ID and crew member ID. No additional email infrastructure needed — SCC handles crew notifications internally.

Calendar Entry Synchronization

A separate Lambda handles JADA Internal Calendar entries. This required:

  • Correct X-Dashboard-Token header (stored in secrets, not in code)
  • Timestamp formatting in ISO 8601 format
  • Separate auth from the SCC service key — each system has its own token

The calendar entry creation was tested by querying the secrets management system for the dashboard token, then crafting a POST to the calendar Lambda with proper headers. This decouples charter booking from internal calendar sync — either could fail independently without breaking the other.

Infrastructure Details

S3 Buckets & CloudFront Distributions

  • sailjada.com S3 bucket: Primary origin for sailjada.com CloudFront distribution
  • queenofsandiego.com S3 bucket: Origin for queenofsandiego.com CloudFront distribution (with Lambda@Edge path rewriting)
  • CloudFront invalidation pattern: /* or specific paths like /g/XHQGMDH.html

Lambda Functions & APIs

  • ShipCaptainCrew Lambda: Handles /events, /events/{id}, and guest presign routes. Fronted by API Gateway (not CloudFront, to preserve custom headers)
  • Dashboard Lambda: Calendar sync endpoint, requires X-Dashboard-Token header
  • JADA Internal Calendar Lambda: Receives calendar entries, must have correct token auth

DynamoDB Direct Updates

A key discovery: when SCC event creation includes sensitive data like revenue splits, crew shouldn't see those details. Rather than relying on Lambda filtering, we directly updated the DynamoDB event record to remove the Revenue and Captain fee fields from the notes after the event was created. This ensures crew never receives those details in magic link emails or crew pages.

Pattern used:


DynamoDB table: scc-events (or equivalent)
Item key: eventId = {BOOKING_ID}
Update: Remove "Revenue" and "Captain fee" attributes
Result: Crew notifications and pages reflect clean charter details only

Key Decisions & Rationale

Why Direct API Gateway Instead of CloudFront?

CloudFront's managed cache policies strip custom headers like Authorization to improve caching. For authenticated APIs, this breaks the request. The solution is to route authentication-required requests directly to the origin (API Gateway) and reserve CloudFront for caching static content only.

Why S3 Flat Files Instead of Nested Directories?

The CloudFront Lambda@Edge function expects files at specific path patterns. Using flat files like guests/XHQGMDH.html avoids ambiguity with nested structures and simplifies the rewrite logic. This is a convention established across the JADA and Queen of San Diego sites.

Why Separate Crew and Guest Pages?

Guest pages (at queenofsandiego.com) are minimal: confirmation, check-in details, photo upload. Crew pages (accessed through SCC after receiving magic links) are full checklists, task lists, and operational details. Splitting these:

  • Keeps guest-facing content clean and non-operational