Building a Dynamic Guest Portal: S3-CloudFront Architecture with Lambda-Driven Event Management
This post documents the technical implementation of a guest-facing charter booking portal integrated with a serverless event management system. The system orchestrates calendar entries, crew notifications, and dynamic guest pages across multiple AWS services and custom infrastructure.
What Was Done
We built an end-to-end booking workflow that:
- Creates a calendar entry in JADA Internal Calendar via Lambda
- Generates a ShipCaptainCrew (SCC) event that auto-notifies crew members with magic links
- Deploys a dynamic guest-facing HTML page to a public S3 bucket with CloudFront distribution
- Removes sensitive revenue data from crew-visible event details
- Maps friendly URLs for guest access independent of booking IDs
Technical Architecture
File Structure & Deployment Pipeline
Guest pages are deployed to queenofsandiego.com CloudFront distribution (not the original sailjada.com bucket). The page path follows a convention-based approach:
- Storage:
s3://queenofsandiego.com/g/XHQGMDH.html - CloudFront Function: Custom rewrite rules strip
/g/prefix and add.htmlextension for clean URLs - Access URL:
https://queenofsandiego.com/g/XHQGMDH(CloudFront function handles path normalization) - Friendly URL:
/g/boatsetter-may-30(routed via SCC frontend routing logic)
The CloudFront function reads the configured distribution ID and applies URL rewriting rules before origin fetch. This eliminates the need for a redirect layer and keeps the guest experience clean.
Lambda Integration Points
Two Lambdas coordinate the booking workflow:
- Dashboard Lambda (
update_dashboard.py): Creates JADA Internal Calendar entries. RequiresX-Dashboard-Tokenheader for authentication. - SCC Lambda: Handles event creation, crew notifications, and dynamic guest page routing. Accepts service key authentication via
Authorization: Bearerheader.
The SCC Lambda environment contains:
SERVICE_KEY_HASH: bcrypt hash of service key for authentication validation- Admin and crew credentials for internal operations
- DynamoDB table references for event persistence
Authentication Flow
The SCC API Gateway endpoint was required because CloudFront strips custom headers. Direct API Gateway access bypasses the CloudFront header stripping issue:
# CloudFront strips custom headers — use API Gateway endpoint
POST https://api-gateway-endpoint.execute-api.us-west-2.amazonaws.com/prod/events
Authorization: Bearer <service-key>
Content-Type: application/json
{
"title": "Boatsetter Charter — May 30",
"start_time": "2025-05-30T09:00:00Z",
"duration_hours": 3,
"crew": ["crew-id-1", "crew-id-2"],
"captain_id": "captain-id",
"notes": "Charter via Boatsetter (no sensitive revenue data for crew view)"
}
The SCC Lambda validates the service key hash before processing, ensuring only authorized clients can create events and trigger crew notifications.
Sensitive Data Handling
Crew members see the SCC event through their crew-facing dashboard. To prevent them from viewing captain fees and net revenue calculations, we:
- Omit revenue and captain fee details from the
notesfield sent to the SCC event - Use a separate internal memo field (not synced to crew view) for financial calculations
- Update DynamoDB event records directly if data needs to be stripped post-creation (via DynamoDB
UpdateItemwith filtered projection)
This ensures the crew-facing interface shows only operational details (times, roles, tasks) without exposing financial terms.
Guest Page Deployment & Presigning
The guest page is generated as a flat HTML file with time-aware photo upload capability:
# Generate guest page with timestamp metadata
echo "<html>...<input type='hidden' value='$(date +%s)'>...</html>" > /tmp/guest-page.html
# Upload to S3 with public-read ACL
aws s3 cp /tmp/guest-page.html s3://queenofsandiego.com/g/XHQGMDH.html --acl public-read
# Invalidate CloudFront cache
aws cloudfront create-invalidation --distribution-id <dist-id> --paths "/g/XHQGMDH.html"
Photo uploads from the guest page are presigned via the SCC Lambda /g/presign endpoint. The SCC Lambda generates time-limited S3 presigned URLs (typically 15-minute expiry) that allow guests to upload charter photos directly to S3:
- Endpoint:
GET /g/{booking_id}/presign - Response: Presigned POST URL valid for 15 minutes
- Storage: Photos uploaded to
s3://queenofsandiego.com/photos/{booking_id}/
Crew Notifications & Magic Links
When an SCC event is created, the Lambda automatically:
- Queries the DynamoDB crew assignments table
- Generates unique magic link tokens (JWT with exp claim)
- Sends crew notifications via SES (configured in Lambda environment)
- Includes direct deep-link to the SCC frontend event page
Each crew member receives an email with a one-click confirmation link that validates the token and marks them as "confirmed" in the event record. No manual login required.
Key Infrastructure Decisions
Why S3 + CloudFront for Guest Pages (Not Direct Lambda Response)
Guest pages are static HTML files because:
- Performance: CloudFront edge caching reduces latency for repeat visits
- Cost: S3 storage + CDN is cheaper than keeping Lambda warm for guest page requests
- Simplicity: No need to manage HTML generation logic in Lambda; templates are versioned in S3
- Offline resilience: Pages remain accessible even if Lambda is unavailable
Why Separate CloudFront Distributions
Guest pages deploy to queenofsandiego.com distribution instead of sailjada.com because:
- Cleaner domain branding for guest-facing content