```html

Automating Charter Booking Workflows: Multi-Service Orchestration Across S3, CloudFront, Lambda, and DynamoDB

When a charter booking arrives through Boatsetter, it triggers a cascade of manual tasks: create a calendar entry, notify crew, generate a guest-facing page, prepare crew checklists, and calculate margins. We automated this entire workflow by orchestrating five AWS services and two static sites, eliminating manual data entry and ensuring consistent crew communication across three domains.

What Was Done

A single booking event now automatically:

  • Creates a JADA Internal Calendar entry with crew assignments and dock details
  • Generates and deploys a guest-facing HTML page to queenofsandiego.com/g/ with real-time photo upload capability
  • Creates a ShipCaptainCrew event that auto-notifies crew with magic-link confirmations
  • Builds a crew-facing checklist page with time-aware task scheduling
  • Calculates net margin after crew fees, captain fees, and port/Sheraton dock charges
  • Sends confirmation emails to all assigned crew with direct deep-links to their event details

The workflow runs across three separate CloudFront distributions and four Lambda functions, unified by a common authentication pattern and shared state in DynamoDB.

Technical Architecture

Multi-Domain S3 + CloudFront Strategy

Guest pages are now deployed to queenofsandiego.com rather than sailjada.com, using a CloudFront function to rewrite /g/SLUG.html requests to /g/SLUG/index.html in the S3 origin. This convention allows flat HTML files (generated per booking) to be accessed as clean URLs:

S3 bucket: queenofsandiego.com
CloudFront distribution: E2XXXXXXXX
Origin path rewrite rule: /g/* → /g/*/index.html

File structure in S3:
/g/xhqgmdh/index.html  (guest page for booking ID xhqgmdh)
/g/xhqgmdh/photos/    (pre-signed upload target)

The rewrite happens in the CloudFront function at the viewer request stage, before the origin receives the request. This approach keeps URLs readable while maintaining the S3 flat-file structure.

Lambda Authentication and Service-to-Service Calls

Three Lambda functions need to communicate securely without relying on IAM roles (which don't traverse CloudFront). We implemented a two-layer auth pattern:

  • Dashboard Lambda (update_dashboard.py) — Protected by X-Dashboard-Token header. Called from local scripts to update SCC events and trigger calendar Lambda.
  • Calendar Lambda (jada_calendar.py) — Called by Dashboard Lambda with the dashboard token, then uses its own SCC API key to create calendar entries.
  • ShipCaptainCrew Lambda (scc_handler.py) — Protected by SERVICE_KEY_HASH environment variable. Accepts requests to POST /events/ to create crew events.

The critical detail: CloudFront strips custom headers when requests pass through it. To work around this, we bypass CloudFront for service-to-service calls by using the direct API Gateway endpoint (not the custom domain). The SCC API Gateway endpoint format is:

https://XXXXXXXX.execute-api.us-west-2.amazonaws.com/production/events/

This endpoint accepts the x-service-key header unmodified, whereas requests through api.shipcrewcaptain.com (CloudFront) would have the header stripped.

DynamoDB Event Model

ShipCaptainCrew events are stored in DynamoDB with a schema that includes:

  • event_id (partition key)
  • event_date, start_time, duration_hours
  • assigned_crew (list of user IDs with roles)
  • notes (initially includes revenue + captain fee; removed before crew view)
  • guest_page_url (link to queenofsandiego.com/g/SLUG/)

Financial details (revenue, crew rates, captain fees) are stored in the notes field during creation but must be removed before the event is visible to crew. This is handled by a PATCH request that updates only the crew-facing fields.

Key Implementation Details

Guest Page Generation and Photo Upload

The guest-facing HTML page is generated dynamically with embedded pre-signed S3 URLs for photo uploads. The signature is time-aware, expiring after 24 hours. The page includes:

  • Charter date, time, and duration (read from booking data)
  • Guest name and contact (pulled from Boatsetter API response)
  • A drag-and-drop photo upload widget that posts directly to S3
  • A read-only display of photos already uploaded (pre-signed GET URLs)
  • Port/dock instructions and parking details

The pre-signed upload URL is generated by the SCC Lambda /g/presign endpoint, which validates the booking ID and returns a temporary PUT signature. The guest page calls this endpoint on page load to get a fresh signature, ensuring uploads work even if the page is cached.

Crew Notification and Confirmation Flow

When the SCC event is created, crew are automatically notified via email with a magic link that deep-links directly to their event details page:

Email subject: "Crew Confirmation: [Boat Name] on [Date]"
Body includes: Date, time, location, assigned role, and confirmation link
Magic link format: queenofsandiego.com/events/[event_id]?token=[crew_magic_token]

The magic token is stored in the crew record in DynamoDB and validated server-side, avoiding the need for username/password authentication.

Margin Calculation Logic

The booking automation includes a margin calculator that:

  • Takes gross charter revenue from Boatsetter
  • Subtracts crew costs: (number of crew × hourly rate × total hours on site)
  • Subtracts captain cost: (captain hourly rate × charter duration)
  • Subtracts port/Sheraton fees: (gross revenue × 18%)
  • Returns net margin to operator

Note: Boatsetter may or may not add a separate captain fee on top of the gross charter price, and may or may not pay that fee through to you. This is configurable in the Boatsetter owner dashboard and should be verified before relying on margin calculations.

Infrastructure Changes

No new resources were created; the workflow reuses existing infrastructure: