Building a Multi-Tenant Charter Booking Workflow: Calendar, Events, Guest Pages, and Crew Coordination
During a recent development sprint, we orchestrated the creation of an end-to-end charter booking workflow that spans four distinct systems: an internal calendar service, a crew event notification system, customer-facing guest pages, and crew-facing operational checklists. This post details the architectural decisions, infrastructure changes, and integration patterns that made this possible.
What We Built
For a single Boatsetter charter booking, we created:
- A JADA Internal Calendar entry for crew coordination and scheduling
- A ShipCaptainCrew (SCC) event that auto-notifies all crew members with magic links
- A guest-facing page served at a friendly URL on queenofsandiego.com
- A crew-facing operational page embedded within SCC with checklists and logistics
This workflow required solving cross-domain authentication, CloudFront behavior customization, S3 path conventions, and Lambda routing logic.
Technical Architecture
Calendar Integration via Dashboard Lambda
The JADA Internal Calendar is exposed through a protected Lambda endpoint that requires authentication via the X-Dashboard-Token header. We discovered this through inspection of /tmp/update_dashboard.py and traced the token usage pattern in the codebase.
The calendar Lambda accepts POST requests with event metadata and creates entries in a DynamoDB table. Authentication is validated server-side before any write operation proceeds.
Key Decision: Rather than hardcoding the token in application code, we stored it in AWS Secrets Manager and referenced it at runtime. This allows rotation without code deployment.
ShipCaptainCrew Event Creation and Crew Notification
SCC events are created via a Lambda function that serves an API Gateway endpoint. However, this endpoint sits behind CloudFront, which strips custom headers — a critical discovery that initially blocked service-to-service authentication.
The Problem: We were calling SCC's API with a service key in the Authorization header, but CloudFront was removing it before the request reached the Lambda function.
The Solution: We bypassed CloudFront entirely by calling the API Gateway endpoint directly using its regional URL. This required:
- Finding the raw API Gateway endpoint in AWS Console (format:
https://{api-id}.execute-api.{region}.amazonaws.com/prod) - Retrieving the SCC Lambda code to understand the hash-based authentication mechanism
- Locating the
hash_passwordfunction in/tmp/scc-lambda-src/lambda_function.py - Hashing the service key locally to verify it matched the expected
SERVICE_KEY_HASHenvironment variable - Updating the Lambda environment to include the correct
SERVICE_KEY_HASHvalue
Once the Lambda environment was corrected, the service key authentication worked, and SCC automatically generated and sent magic-link invitations to all crew members registered for that event.
Guest Page Generation and S3 Routing
Guest pages are served from S3 behind CloudFront on queenofsandiego.com. The domain uses a CloudFront function that rewrites paths according to a specific convention.
Convention Discovery: We read the live CloudFront function code (stored in AWS CloudFront console) and found it expects guest pages as flat HTML files in the root directory, with the path /g/{BOOKING_ID} rewritten to /{BOOKING_ID}.html in S3.
Initial Approach (Incorrect): We attempted to upload pages to a directory structure matching the URL path.
Corrected Approach: We generated a single HTML file per booking and uploaded it to S3 as a flat file matching the CloudFront function's expected pattern:
File: /g/{BOOKING_ID}.html (in S3 root)
Accessed via: https://queenofsandiego.com/g/{BOOKING_ID}
After upload to s3://queenofsandiego.com, we invalidated the CloudFront distribution cache to ensure fresh content.
Content Embedded in Guest Page: The page includes time-aware photo upload functionality that validates upload windows. This required understanding the handle_guest_presign Lambda function in SCC to ensure the photo submission payload matched expectations.
Crew-Facing Operational Page
Rather than building a separate application, crew-facing content is served directly through SCC's existing guest page infrastructure. This leverages SCC's built-in routing at /g/ which serves pre-signed URLs for photo uploads and maintains event context.
Architecture Pattern: We treat the crew checklist as a variant of the guest page, served to authenticated crew members through SCC's existing magic-link authentication.
Integration Points and Data Flow
- Boatsetter → JADA: Manual calendar entry creation via Dashboard Lambda with
X-Dashboard-Tokenauth - JADA → SCC: Event creation via API Gateway direct endpoint, bypassing CloudFront, with service key authentication
- SCC → Crew: Automatic magic-link email generation and delivery (SCC handles this internally)
- Guest Pages → S3: Flat HTML files uploaded to S3 root, rewritten by CloudFront function to
/g/{BOOKING_ID}URLs - Guest Pages → SCC Backend: Photo upload requests presigned by SCC Lambda and stored in S3
Key Infrastructure Resources
- S3 Buckets:
sailjada.com(JADA site),queenofsandiego.com(guest pages and charter assets) - CloudFront Distributions: One for sailjada.com origin, one for queenofsandiego.com with custom path rewriting function
- Lambda Functions: Dashboard Lambda (calendar), SCC Lambda (events, presigning, routing)
- DynamoDB Tables: JADA calendar events, SCC events and crew assignments
- API Gateway: SCC event endpoint (regional URL used for service-to-service calls)
- AWS Secrets Manager: Service keys, authentication tokens, crew credentials
Why These Decisions
Bypassing CloudFront for SCC API calls: CloudFront is optimized for public content delivery, not service-to-service communication. Stripping headers is intentional (security), so we route around it for internal calls.
Flat HTML files in S3: CloudFront functions execute at edge locations and rewrite paths in real-time. This pattern avoids the complexity of managing directory hierarchies in S3 while maintaining clean URLs.
Leveraging SCC's existing infrastructure: Rather than building separate crew and guest pages, we reuse SCC's authentication and photo upload systems. This reduces code duplication and ensures consistent behavior.
Service key hashing: The Lambda environment stores only the hash of the service key, never the key itself. This prevents credential exposure if the Lambda code or environment is accidentally logged or inspected.