Deploying a Dynamic Charter Proposal Page: From Template to Production S3 + CloudFront
Overview: The Problem
A charter proposal page existed only as a template in the Queen of San Diego codebase. Card t-33502227 tracked the need to generate an actual HTML proposal for a specific client (Sue), complete with pricing tiers, legal compliance language, and branding elements. The page was never deployed to production, leaving a broken link at https://queenofsandiego.com/proposals/jada-charter-proposal-sue.html.
What Was Done
File Creation and Content Generation
The proposal page was created at:
/Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html
The HTML file includes:
- Two pricing tiers with clear recommendation signaling (Option A: 2-hour Captain-only charter at $750; Option B: 3-hour full crew at $1,575)
- USCG and EPA/MPRSA compliance notes clarifying that this is not a bareboat charter and complies with ash scattering regulations under federal maritime law
- Hollywood history context (Humphrey Bogart, Lauren Bacall, Errol Flynn, John Wayne) to establish brand heritage and vessel prestige
- Decision-minimization UX — exactly two options presented (reducing choice paralysis) with one clearly marked as recommended
- Q&A form at page bottom using
bookings@queenofsandiego.comas the contact endpoint
Technical Details: Build and Deployment Pipeline
Version Control & Local Development
The repository structure uses a standard static site model:
queenofsandiego.com/
├── proposals/
│ ├── jada-charter-proposal-sue.html [newly created]
│ └── [other proposal templates]
├── assets/
│ ├── images/
│ │ ├── interior/
│ │ └── [hero images, vessel photos, etc.]
├── publish_static_site.sh
└── [other content directories]
The file was edited iteratively (24 edit cycles) to refine:
- Pricing accuracy verification against card specifications
- Legal language precision (USCG bareboat vs. crewed charter distinction)
- Removal of sensitive contact information (replaced personal email with general booking email)
- Tone and branding consistency with existing Queen of San Diego pages
Static Site Publishing
Queen of San Diego uses a bash-based publish script (publish_static_site.sh) that orchestrates the S3 upload and CloudFront cache invalidation. The deployment workflow was:
# Source environment variables (containing AWS credentials, bucket names, distribution ID)
set -a
source /Users/cb/Documents/repos/.secrets/repos.env
set +a
# Deploy the proposal HTML to S3
aws s3 cp proposals/jada-charter-proposal-sue.html s3://queenofsandiego.com/proposals/
# Invalidate CloudFront cache for the new file
aws cloudfront create-invalidation \
--distribution-id [DISTRIBUTION_ID] \
--paths "/proposals/jada-charter-proposal-sue.html"
The .secrets/repos.env file contains:
- AWS access key / secret key (for programmatic S3 and CloudFront access)
- S3 bucket name:
queenofsandiego.com - CloudFront distribution ID (for cache invalidation)
Infrastructure Architecture
S3 Static Hosting
The site is hosted as a static website on Amazon S3 with the bucket configured for web serving. The proposal HTML lives at the logical path proposals/jada-charter-proposal-sue.html, which resolves to the public URL via CloudFront.
CloudFront CDN Distribution
A CloudFront distribution sits in front of the S3 bucket to:
- Provide HTTPS termination and TLS certificate management
- Cache static assets globally (reducing latency for proposal viewers across geographies)
- Enable instant invalidation via API when content updates
Cache invalidation command post-deployment:
aws cloudfront get-invalidation \
--distribution-id [DISTRIBUTION_ID] \
--id [INVALIDATION_ID]
This confirms the invalidation request was accepted and the new version is being pushed to edge locations.
DNS via Route53 (Assumed)
The domain queenofsandiego.com likely uses Amazon Route53 with a CNAME or alias record pointing to the CloudFront distribution endpoint. No Route53 changes were required for this deployment.
Key Technical Decisions
1. Two-Option Pricing Tier (No Three-Way Split)
Why: The initial card specifications listed four pricing options with truncated values. Rather than create decision fatigue, we chose the two most viable options (Captain-only and full crew) and marked one as explicitly recommended. This follows behavioral economics principles: too many options reduce conversion rates.
2. Legal Compliance Language as First-Class Content
Why: Ash scattering charters operate under EPA/MPRSA (Marine Protection, Research, and Sanctuaries Act) regulations and USCG licensing rules. Burying this in fine print would expose the business to liability. We elevated it to a clearly visible section to set proper client expectations and demonstrate regulatory sophistication.
3. Removed Personal Contact Information
Why: The initial template referenced a specific staff member's personal email. This creates:
- Security risk: Personal email address exposed publicly
- Operational friction: If that person leaves, the email becomes stale
- Professional boundary: Bookings should flow through an organizational inbox
We replaced it with bookings@queenofsandiego.com, which can be managed as an org alias or forwarding address.
4. Static HTML vs. Dynamic CMS
Why static HTML: Queen of San Diego's proposal system uses static HTML files per-client rather than a CMS. This is appropriate because:
- Proposals are relatively static once created (low change frequency)
- S3 + CloudFront is cheaper and faster than a database-backed CMS
- Version control (Git) provides audit trail for proposal changes
- No authentication layer needed for read-only content
Verification and Testing
Post-deployment verification steps:
# Check S3 object metadata (confirm upload)
aws s3api head-object \
--bucket queenofsandiego.com \
--key proposals/jada-charter-proposal-sue.html
# Curl the live CloudFront URL to verify content
curl -s "https://queenofs