```html

Multi-Domain Guest Booking Workflow: Coordinating S3, CloudFront, Lambda, and DynamoDB for Charter Event Management

This post documents the architecture and implementation decisions behind a unified guest booking system that coordinates multiple AWS services, custom Lambda handlers, and domain routing logic to create a seamless experience for charter crew coordination.

Problem Statement

The core challenge: when a new charter booking arrives (via Boatsetter or direct), we need to simultaneously:

  • Create an internal calendar entry for operations planning
  • Notify crew automatically with authenticated magic links
  • Generate and host a guest-facing confirmation page
  • Provide crew with a detailed checklist page for prep
  • Track financials and crew assignments

The workflow had to work across two separate domains (sailjada.com and queenofsandiego.com), use existing CloudFront distributions with custom function rewrites, and maintain secure authentication without leaking sensitive data to frontend consumers.

Technical Architecture

Domain and S3 Organization

Guest-facing pages are hosted on queenofsandiego.com (CloudFront distribution ID: E***), with the S3 origin bucket queenofsandiego.com. This separation from sailjada.com serves multiple purposes:

  • Brand separation: Guests see a branded interface separate from crew tools
  • Subdomain routing: Pages are served at /g/{BOOKING_ID}.html paths
  • Friendly URLs: Custom mapping from booking ID to human-readable slugs (e.g., /g/boatsetter-may30.html)

The CloudFront function (attached to viewer request) implements path rewriting logic:

// CloudFront Function: queenofsandiego.com path rewriting
// Maps /g/ requests to flat .html files per S3 convention
if (request.uri.startsWith('/g/')) {
  request.uri = request.uri + '.html'; // if not already .html
}

This allows clean URLs while maintaining S3 flat-file structure without folder hierarchies.

Lambda Event Coordination

The ShipCaptainCrew Lambda (/tmp/scc-lambda-src/lambda_function.py) handles event creation via two distinct pathways:

  • CloudFront path: Via queenofsandiego.com domain (header stripping issue)
  • Direct API Gateway: Bypassing CloudFront to preserve auth headers

The Lambda service key authentication uses environment variable SERVICE_KEY_HASH (bcrypt hash of actual key). Requests include:

POST /event
X-Service-Key: {SERVICE_KEY}
Content-Type: application/json

{
  "event_name": "May 30 Boatsetter Charter",
  "crew_ids": ["crew-001", "crew-002"],
  "captain_id": "captain-001",
  "date": "2024-05-30T09:00:00Z",
  "duration_hours": 3,
  "notes": "Guest count: 6, Dock: Sheraton Marina"
}

The Lambda validates the key, creates a DynamoDB record in the scc_events table, and auto-generates magic link tokens for crew notifications.

Guest Page Generation and Hosting

Guest pages are generated as static HTML files and uploaded directly to s3://queenofsandiego.com/g/. The file naming convention:

// Booking ID example: xhqgmdh (7-char alphanumeric)
s3://queenofsandiego.com/g/boatsetter-may30.html
// Also kept as fallback:
s3://queenofsandiego.com/g/xhqgmdh.html

CloudFront caching is invalidated immediately after upload using invalidation requests against the distribution.

Authentication and Security Decisions

Why Hash Service Keys in Lambda Env

Storing plaintext keys in Lambda environment variables is a security anti-pattern. Instead:

  • The actual service key is stored in AWS Secrets Manager
  • Lambda env var SERVICE_KEY_HASH contains the bcrypt hash
  • Incoming requests are hashed on-the-fly and compared: bcrypt.checkpw(request_key, env_hash)
  • Logs never reveal the actual key

Why Bypass CloudFront for API Calls

CloudFront functions strip non-standard headers (including custom auth headers like X-Service-Key). Solution: hit the API Gateway endpoint directly instead of routing through the CDN:

// Instead of:
POST https://queenofsandiego.com/event

// Use:
POST https://{API_GATEWAY_ID}.execute-api.us-west-2.amazonaws.com/event

This preserves auth headers while API Gateway resource policies ensure only internal services (dashboard Lambda, scheduled tasks) can invoke it.

Data Flow and Crew Notification

When an event is created, the Lambda:

  1. Validates the service key
  2. Writes event record to DynamoDB (scc_events table)
  3. Generates unique magic link tokens for each crew member
  4. Invokes SES or sends via integrated notification system
  5. Email includes deep link: https://queenofsandiego.com/crew/xhqgmdh?token={MAGIC_TOKEN}

Magic tokens are single-use and time-limited (24–48 hours) to prevent unauthorized access.

Financial Data Sensitivity

A critical security decision: revenue and captain fee information must NOT appear in crew-facing event notes. During booking creation, notes are sanitized before storage:

// In Lambda event handler:
event_notes = sanitize_for_crew_visibility(notes)
// Removes: revenue, captain_fee, net_margin
// Keeps: guest count, special requests, dock location

If historical events had sensitive data, a DynamoDB direct update can remove it retroactively (for PATCH operations).

Key Infrastructure Resources

  • S3 buckets: sailjada.com (internal tools), queenofsandiego.com (guest pages)
  • CloudFront distributions: E*** for queenofsandiego.com with viewer request function
  • Lambda functions: ShipCaptainCrew handler, dashboard update trigger, calendar integration
  • DynamoDB tables: scc_events, crew, bookings
  • API Gateway: Resource-based policy restricting to internal callers
  • Route53: queenofsand