Multi-Domain Event Management Architecture: Boatsetter Charter Booking Automation
This post documents the architectural decisions and implementation details for automating charter booking workflows across multiple domains (sailjada.com, queenofsandiego.com) and services (ShipCaptainCrew platform, JADA Internal Calendar, guest-facing pages). The system handles financial calculations, crew notifications, and guest experience pages with minimal manual intervention.
What Was Done
- Created a unified event lifecycle spanning three separate services: ShipCaptainCrew (SCC) Lambda-backed API, JADA Internal Calendar (Lambda + DynamoDB), and static guest pages (CloudFront + S3)
- Implemented cross-domain page routing using CloudFront functions to serve flat .html files at SEO-friendly URLs
- Solved authentication challenges across service boundaries (dashboard token headers vs. service key hashing)
- Deployed crew notification system that auto-generates magic links via Lambda environment variables
- Moved guest-facing pages from sailjada.com (original domain) to queenofsandiego.com (branded subdomain) using S3 bucket switching and CloudFront invalidation
Technical Architecture
Service Integration Layer
The charter booking workflow triggers across three independent systems, each with distinct authentication mechanisms:
- ShipCaptainCrew Lambda API (`/tmp/scc-lambda-src/lambda_function.py`): Event CRUD operations protected by SERVICE_KEY_HASH environment variable. Crew notifications are auto-triggered via event creation, generating personalized magic links from Lambda env vars (CREW_INVITE_BASE_URL).
- JADA Internal Calendar Lambda: REST API requiring X-Dashboard-Token header. Called via direct API Gateway endpoint (not CloudFront) to bypass header stripping that occurs in CloudFront Functions.
- Guest Page Generation: Static HTML uploaded to S3 with time-aware photo upload handlers. Pages are served via CloudFront with rewrite rules defined in CF Functions.
Why This Approach? Each service evolved independently with different auth requirements. Rather than refactoring all three, we route around CloudFront header stripping by using API Gateway direct URLs for services that need custom headers. This minimizes blast radius while maintaining isolation of concerns.
Authentication Challenge: Service Keys vs. Dashboard Tokens
Initial SCC event creation failed because the service key was being stripped by CloudFront before reaching the Lambda origin. The CloudFront Function (defined in the distribution config) was designed to strip custom headers for security.
Solution: Route SCC API calls directly to API Gateway endpoint instead of through CloudFront distribution. This bypasses the header-stripping function and allows the SERVICE_KEY_HASH validation in the Lambda to execute correctly.
# Pseudo-command: Get SCC API Gateway endpoint
aws apigateway get-rest-apis --query 'items[?name==`ShipCaptainCrew-API`].id' --output text
# Direct API call (not through CloudFront)
curl -X POST https://api-gateway-id.execute-api.region.amazonaws.com/prod/events \
-H "Authorization: Bearer SERVICE_KEY" \
-d '{"event_data": {...}}'
CloudFront Function: Path Rewriting for Guest Pages
Guest pages are stored in S3 as flat .html files (e.g., `/tmp/jada-guest-xhqgmdh.html`) but served at SEO-friendly URLs like `/g/boatsetter-may-30-charter`. This requires a CloudFront Function to rewrite paths before hitting the origin.
The function in the queenofsandiego.com distribution examines incoming requests to `/g/*` and appends `.html` before querying S3:
// CloudFront Function pseudo-logic (read from distribution)
if (request.uri.startsWith('/g/')) {
request.uri = request.uri + '.html';
}
return request;
Guest pages are uploaded to the S3 bucket backing queenofsandiego.com (not sailjada.com) to consolidate the branded experience. After upload, a CloudFront invalidation is issued on distribution ID `QUEENOFSANDIEGO_DIST_ID` to purge cache.
Event State Management Across Services
A single charter booking generates three independent records:
- SCC Event: Contains crew assignments, notes (originally included revenue but later removed to hide earnings from crew), and magic invite links. Updated via PATCH to `/events/{eventId}` in Lambda.
- JADA Calendar Entry: Internal scheduling record created via `X-Dashboard-Token` protected endpoint. Syncs with crew availability tracking.
- Guest Page: Static HTML snapshot at creation time. No sync mechanism — this is intentional because guest experience should be immutable once deployed.
Why separate records? Each service serves a different audience (crew internal tools vs. guest-facing experience). Keeping them decoupled means a change to the guest page doesn't trigger event updates that might accidentally expose sensitive data (like captain fees or revenue splits).
Infrastructure Details
S3 Buckets
sailjada.com: Original charter site. Contains legacy guest pages. Being phased out as new pages move to queenofsandiego.com.queenofsandiego.com: Branded subdomain. New guest pages uploaded here. Backed by CloudFront distribution `QUEENOFSANDIEGO_DIST_ID`.
CloudFront Distributions
- sailjada.com distribution: Origin points to S3 bucket. CloudFront Function strips custom headers (security posture).
- queenofsandiego.com distribution: Origin points to S3 bucket. CloudFront Function rewrites `/g/*` paths to append `.html` before hitting origin.
Lambda Functions
ShipCaptainCrew-Lambda: Event CRUD + crew notifications. Environment variables include SERVICE_KEY_HASH (bcrypt hash of service key) and CREW_INVITE_BASE_URL (for magic links). Stores events in DynamoDB table `Events`.JADA-Calendar-Lambda: Calendar entry creation. Protected by X-Dashboard-Token. DynamoDB table `Calendar`.
API Gateway
- ShipCaptainCrew API Gateway: Direct endpoint used to bypass CloudFront header stripping. Accessible at `https://api-{id}.execute-api.{region}.amazonaws.com/prod/`
- JADA Dashboard API: Also uses direct API Gateway endpoint to preserve X-Dashboard-Token header.
Financial Calculation & Automation
The charter booking system captures the following math automatically:
- Gross revenue from Boatsetter: $840.75
- Crew payroll: 2 crew × $25/hr × 5 hrs = $250.00
- Captain payroll: 1 captain × $50/hr × 3 hrs = $150.00
- Port/Sheraton fee: 18% of gross = $151.34
- Net margin: $