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: