Automating Charter Booking Infrastructure: Lambda, S3, CloudFront, and API Gateway Integration

This post documents a complete automation workflow built to handle charter bookings across multiple systems: Boatsetter intake → JADA Internal Calendar → ShipCaptainCrew event system → guest-facing pages → crew notifications. The architecture demonstrates practical patterns for event-driven orchestration, cross-domain API communication, and static asset delivery in a serverless environment.

What Was Done

A charter booking (3-hour Boatsetter engagement, $840.75 gross) triggered a multi-step automation sequence:

  • Created a JADA Internal Calendar entry (May 30 booking with crew/captain assignments)
  • Generated a ShipCaptainCrew (SCC) event that auto-notifies all crew with magic link authentication
  • Built a guest-facing page at /g/<event-id>.html and uploaded to S3
  • Created a crew-facing checklist page with booking details
  • Sent crew confirmation emails via SES with proper authentication headers

Key finding: Charter net margin after crew ($250 @ 5hrs), captain ($150 @ 3hrs), and port fees (18% = $151.34) is $289.41 (~34% of gross, before fuel).

Technical Details: API Authentication Across Domains

The most significant technical challenge was coordinating authenticated API calls across three separate Lambda functions and an API Gateway, each with different authentication mechanisms.

Dashboard Lambda Authentication

The dashboard Lambda (which creates JADA Calendar entries) uses a custom header-based token:

X-Dashboard-Token: <token-value>

This token is stored in AWS Secrets Manager and retrieved at runtime. The Lambda validates the token in its handler before processing calendar creation requests. Command to locate the token source:

aws secretsmanager get-secret-value --secret-id jada/dashboard-lambda --region us-east-1

The calendar entry creation happens via an internal Lambda invocation, not HTTP—this bypasses API Gateway entirely and uses IAM role permissions instead.

ShipCaptainCrew Service Key Authentication

The SCC system uses a service key hash-based approach stored in Lambda environment variables:

  • Service key is stored in Secrets Manager: sailjada/scc-service-key
  • Lambda environment variable SERVICE_KEY_HASH contains the bcrypt hash
  • API calls hash the incoming service key and compare: if match, request is authenticated

The SCC Lambda code location: lambda/ShipCaptainCrew/index.js (or Python equivalent in update_dashboard.py).

Initially, the hash comparison was failing because the SERVICE_KEY_HASH environment variable wasn't set. The fix required updating the Lambda function configuration via CloudFormation or the console.

CloudFront Header Stripping Issue

A critical discovery: CloudFront distribution sailjada.com (with origin S3 bucket sailjada.com) was stripping custom headers, preventing service key authentication on API calls routed through the distribution.

Solution: Bypass CloudFront for API calls by hitting the API Gateway directly:

https://<api-gateway-id>.execute-api.us-east-1.amazonaws.com/prod/events

instead of:

https://api.sailjada.com/events

This is a common pattern when CloudFront is configured with restrictive cache behaviors. The tradeoff: direct API Gateway calls don't benefit from CloudFront caching, but for low-frequency API calls, this is acceptable.

Infrastructure: File Paths and Resource Names

S3 Bucket Structure

Guest-facing pages are stored in s3://sailjada.com/g/ with CloudFront distribution ID <CF-DIST-ID> serving as origin.

s3://sailjada.com/
├── g/
│   └── jada-guest-xhqgmdh.html (newly created guest page)

After uploading, CloudFront cache must be invalidated:

aws cloudfront create-invalidation --distribution-id <CF-DIST-ID> --paths "/g/*"

Lambda Functions Involved

  • Dashboard Lambda: Handles JADA Calendar creation. Environment variable: CALENDAR_TABLE (DynamoDB table name). Invoked via boto3.client('lambda').invoke() with payload containing event details.
  • SCC Lambda: Processes event creation and crew notifications. Path: typically in lambda/ShipCaptainCrew/ with handler index.handler. Auto-sends magic link emails to crew via SES.
  • GAS Email Endpoint: Google Apps Script endpoint used for email summary to Carole (booking manager). Called via HTTPS POST.

DynamoDB Tables

JADA Internal Calendar stores entries in a DynamoDB table (exact name retrieved from Lambda environment). Partition key is likely event_id, with sort key for date-based queries.

Key Architectural Decisions

Why Direct Lambda Invocation for Calendar?

Instead of HTTP calls, calendar creation uses boto3.client('lambda').invoke(). Reasons:

  • No API Gateway overhead or HTTP latency
  • IAM-based auth (service role permissions) is more secure than token-based
  • Synchronous invocation ensures the calendar entry exists before crew notifications send

Why API Gateway Direct Call for SCC?

SCC events go through API Gateway directly (not CloudFront) because:

  • CloudFront strips custom headers, breaking service key authentication
  • Events are infrequent enough that cache efficiency doesn't justify the complexity
  • Future: Add a CloudFront cache behavior rule allowing service key headers, or move API domain to Route53 alias pointing directly to API Gateway

Guest Page as Static HTML

The guest-facing page is a static HTML file uploaded to S3, not generated dynamically. Reasons:

  • Fast delivery via CloudFront CDN
  • No backend compute cost
  • Can be archived indefinitely without storage overhead
  • SEO-friendly (static HTML, no JS framework hydration needed)

Command Examples (No Secrets)

Verify S3 bucket configuration:

aws s3api get-bucket-versioning --bucket sailjada.com
aws s3api get-bucket-cors --bucket sailjada.com

Check CloudFront cache behaviors:

aws cloudfront get-distribution-config --id <CF-DIST-ID>

Verify Lambda environment variables