Building a Dynamic Charter Proposal System: From Template to Live Deployment
Overview
This post documents the technical implementation of a dynamic charter proposal page for a San Diego sailing charter business. The challenge: a proposal template existed but was never instantiated as a live HTML page, causing broken links and lost revenue opportunities. The solution involved creating a templated proposal system, integrating it into the existing static site infrastructure, and deploying it through S3 + CloudFront with proper cache invalidation.
The Problem: Template Orphanage
During development, we discovered that the file /proposals/jada-charter-proposal-sue.html was being referenced in business logic and customer communications, but the actual HTML file did not exist in the repository at /Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html. The template existed elsewhere, but was never hydrated with actual pricing, legal compliance language, and marketing content for this specific charter offering.
Impact: Dead links sent to customers, no conversion path, and incomplete sales funnel for the core product line.
Technical Implementation
File Structure & Asset Organization
The site uses a static site generator pattern with the following structure:
/Users/cb/Documents/repos/sites/queenofsandiego.com/
├── proposals/
│ └── jada-charter-proposal-sue.html [NEW]
├── assets/
│ ├── images/
│ │ ├── interior/ [Vessel photos]
│ │ └── [other image assets]
│ ├── styles/
│ └── scripts/
├── publish_static_site.sh [Deployment script]
└── [other site content]
The decision to place the proposal in a dedicated /proposals/ directory (rather than mixing with general site content) allows for:
- Easy permission scoping for future proposals (multiple vessels, charter types)
- Logical separation of transactional pages from marketing content
- Simplified tracking and analytics (all proposals under one path prefix)
Content Architecture: Two-Option Decision Model
Rather than overwhelming potential customers with excessive choice, the proposal implements a deliberately constrained decision tree:
- Option A (Recommended): 2-hour captain-only charter at $750 flat rate
- Option B: 3-hour full-crew charter at $1,575 flat rate
Why two options? This follows the "Goldilocks principle"—research on choice architecture shows that 2-3 well-curated options reduce decision paralysis while maintaining perceived autonomy. Pricing tiers were established through cost analysis of crew scheduling, fuel, and operational overhead.
Regulatory & Legal Compliance Integration
The proposal includes mandatory compliance language for EPA/MPRSA ash scattering regulations and USCG licensing disclaimers. These are embedded directly in the HTML (not in a separate modal or external document) to ensure:
- Compliance visibility during the decision-making process
- No external dependencies on PDF hosting or email attachments
- Proper HTML semantic structure for accessibility and SEO
This approach avoids the common pitfall of burying legal terms in footnotes or separate documents—customers see full context when considering options.
Deployment Pipeline
Static Site Publishing
The deployment process uses a bash script located at /Users/cb/Documents/repos/sites/queenofsandiego.com/publish_static_site.sh. The script:
- Syncs the local repository to an S3 bucket (
queenofsandiego.com) - Uses AWS CLI with proper IAM role-based credentials (sourced from
/Users/cb/Documents/repos/.secrets/repos.env) - Applies appropriate cache headers (CloudFront, browser cache)
- Invalidates the CloudFront distribution to purge edge caches
Command pattern (credentials omitted):
cd /Users/cb/Documents/repos/sites/queenofsandiego.com
set -a; source /Users/cb/Documents/repos/.secrets/repos.env; set +a
aws s3 cp proposals/jada-charter-proposal-sue.html \
s3://queenofsandiego.com/proposals/jada-charter-proposal-sue.html \
--content-type "text/html; charset=utf-8"
CDN Cache Invalidation
After deploying to S3, CloudFront cache invalidation is critical for immediate live updates. The distribution ID is stored in environment variables (not hardcoded). The invalidation command:
aws cloudfront create-invalidation \
--distribution-id [DIST_ID] \
--paths "/proposals/jada-charter-proposal-sue.html" "/proposals/*"
Why wildcard invalidation on /proposals/*? This ensures any related proposal assets (images, metadata tags, related links) are also purged from edge locations globally, preventing stale content from being served during the proposal lifecycle.
Verification Steps
Post-deployment verification uses:
curl -s "https://queenofsandiego.com/proposals/jada-charter-proposal-sue.html" | grep -E "specific-content-to-verify"
This confirms the live page is serving the correct version before customer communication.
Architecture Decisions & Trade-offs
Static HTML vs. Dynamic Server
Decision: Static HTML file deployed to S3 + CloudFront, not a server-rendered template.
Rationale:
- Cost: S3 + CloudFront is ~$0.01/GB with massive scale-out; no server overhead
- Performance: Edge caching at 200+ CloudFront locations globally; zero Time To First Byte beyond network latency
- Operational simplicity: No servers to patch, scale, or monitor; version control via git is complete audit trail
- Trade-off: Cannot dynamically change pricing or legal text without redeployment; mitigated by templating the HTML source and rebuilding when needed
Single vs. Multiple Proposal Files
The proposal uses a single dedicated file (jada-charter-proposal-sue.html) rather than a templated proposal engine with query parameters. This is intentional:
- Named files are bookmarkable and shareable; query-param variants are harder to track in customer communications
- Separate files per proposal enable granular analytics (Google Analytics, Mixpanel, etc.)
- Simplifies legal/compliance tracking—each proposal version is a distinct git commit
- Future scaling: if 5+ proposals exist, a template engine would be reconsidered, but at current scale this is YAGNI (You Aren't Gonna Need It)
Content Scope: Marketing vs. Technical
The proposal includes historical context (Hollywood history of the vessel) alongside technical specs. This is deliberate: