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.comvia 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_HASHwas set correctly - Confirming the Lambda's
hash_passwordfunction 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-Tokenheader (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.comS3 bucket: Primary origin forsailjada.comCloudFront distributionqueenofsandiego.comS3 bucket: Origin forqueenofsandiego.comCloudFront 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, requiresX-Dashboard-TokenheaderJADA 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