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>.htmland 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_HASHcontains 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 viaboto3.client('lambda').invoke()with payload containing event details. - SCC Lambda: Processes event creation and crew notifications. Path: typically in
lambda/ShipCaptainCrew/with handlerindex.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