```html

Building a Multi-Tenant Charter Booking Pipeline: Guest Pages, Event Coordination, and CloudFront Path Routing

When a charter booking comes in through Boatsetter, it's the starting gun for coordinated logistics: crew confirmation, calendar management, guest communications, and financial tracking. This post details the infrastructure and architectural decisions made to automate the entire pipeline—from S3 guest page deployment to DynamoDB event records to crew notification.

The Problem: Manual Coordination Across Multiple Systems

A single booking required:

  • Creating an internal calendar entry in JADA Internal Calendar
  • Creating a ShipCaptainCrew (SCC) event that auto-notifies crew via magic links
  • Building and hosting a guest-facing page with photo upload capability
  • Generating a crew-facing page with checklists and logistics
  • Notifying all crew for confirmation
  • Tracking revenue and crew costs

This was happening across four separate systems (JADA calendar, SCC platform, S3/CloudFront for guest pages, email for crew notifications). The goal: automate the entire flow with a single trigger.

Architecture: Event-Driven Pipeline with Lambda + DynamoDB + S3

The solution uses AWS Lambda as the orchestration layer, with three distinct API integrations:

  • Dashboard Lambda (JADA calendar): Authenticated with X-Dashboard-Token header
  • SCC Lambda (ShipCaptainCrew): Authenticated with X-Service-Key header and bcrypt-hashed verification
  • S3 + CloudFront: Guest page hosting with path-rewrite logic

Technical Implementation: Guest Page Deployment Pattern

File: /tmp/jada-guest-xhqgmdh.html

The guest page is a self-contained HTML file deployed to S3 with a specific naming convention. Rather than using URL path parameters (e.g., /g/xhqgmdh/), the CloudFront function rewrites requests to flat HTML files stored in S3:

S3 location: s3://queenofsandiego.com/g/XHQGMDH.html
CloudFront distribution: E3L9KXYZ1A2B (queenofsandiego.com)
Accessible via: https://queenofsandiego.com/g/XHQGMDH

Why this pattern? CloudFront Functions strip custom headers before reaching origin (S3), so authentication tokens can't be passed through. By using flat filenames and CF rewrite logic, we bypass header concerns entirely. The booking ID becomes the filename, eliminating the need for server-side routing.

CloudFront Function Logic (live in distribution):

Request URI: /g/XHQGMDH
CF Function rewrites to: /g/XHQGMDH.html
S3 serves: queenofsandiego.com/g/XHQGMDH.html

The guest page includes:

  • Booking summary and itinerary
  • Time-aware photo upload form (uses presigned SCC Lambda endpoint)
  • Links to crew confirmation and internal calendar
  • Guest safety and house rules

Event Creation: SCC Lambda Integration

File: /tmp/scc-lambda-src/lambda_function.py

The SCC Lambda handles three responsibilities:

  • Event creation with crew auto-notification
  • Presigned URL generation for guest photo uploads
  • Event patching for updates (revenue removal, notes modification)

Authentication Challenge: The SCC Lambda uses X-Service-Key header authentication with bcrypt verification. The service key is hashed and stored in Lambda environment variables:

Lambda environment: SERVICE_KEY_HASH
Incoming request: X-Service-Key header
Lambda logic: bcrypt.verify(incoming_key, SERVICE_KEY_HASH)

Critical Discovery: Initial requests failed because CloudFront was stripping the X-Service-Key header. Solution: Hit the API Gateway endpoint directly (bypassing CloudFront) or use AWS Signature Version 4 signing if calling through CloudFront.

Event Creation Payload:

{
  "action": "create_event",
  "event_name": "Boatsetter Charter - May 30",
  "start_time": "2024-05-30T09:00:00Z",
  "duration_hours": 3,
  "crew_ids": ["crew1", "crew2"],
  "captain_id": "captain1",
  "notes": "Guest checklist at queenofsandiego.com/g/XHQGMDH",
  "revenue": 840.75,
  "crew_rate": 25,
  "captain_rate": 50
}

This automatically triggers SCC's crew notification system, sending magic-link confirmations to all assigned crew.

Calendar Integration: JADA Dashboard Lambda

Endpoint: Internal dashboard Lambda (URL from secrets configuration)

Authentication: X-Dashboard-Token header with bearer token from Lambda environment

curl -X POST https://dashboard.internal/api/calendar \
  -H "X-Dashboard-Token: Bearer $DASHBOARD_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "calendar_id": "jada-internal",
    "title": "Boatsetter Charter - May 30",
    "start": "2024-05-30T09:00:00Z",
    "duration_minutes": 180,
    "metadata": {
      "guest_page": "https://queenofsandiego.com/g/XHQGMDH",
      "revenue": 840.75,
      "crew_cost": 250.00,
      "captain_cost": 150.00,
      "port_fee": 151.34,
      "net_profit": 289.41
    }
  }'

Crew Notification: Magic Links vs. Traditional Email

Rather than sending emails with attachments or links, the SCC event creation triggers the platform's built-in crew notification system. This sends magic-link confirmation emails that:

  • Authenticate crew without passwords
  • Deep-link directly to the SCC event detail page
  • Include pre-filled checklists and assignments
  • Track acceptance/rejection in DynamoDB

Why magic links over direct email? Reduces context-switching: crew don't leave email to manage the booking. They click the link, confirm availability in SCC, and see all logistics in one place.

Financial Reconciliation: Revenue Scrubbing in DynamoDB

After event creation, revenue and captain fees are removed from the crew-visible event notes (via DynamoDB direct update) because crew shouldn't see charter earnings. This prevents wage expectation disputes and keeps compensation discussions between captain and crew.

DynamoDB operation:

Table: