```html

Deploying a Dynamic Charter Proposal System: From Static Template to Live S3+CloudFront Architecture

Overview: The Problem

A charter proposal template existed in the codebase at /Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html, but the actual rendered file was never deployed to the live S3 bucket. This meant requests to queenofsandiego.com/proposals/jada-charter-proposal-sue.html would fail or serve stale content. The task: author the proposal HTML, deploy it to S3, invalidate the CloudFront cache, and verify live delivery.

What Was Done

  • Created/Modified: /Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html — multi-pass HTML authoring with pricing tiers, legal compliance language, and embedded Q&A form
  • Deployed: Pushed to S3 bucket (sourced from .secrets/repos.env) using AWS CLI
  • Cache Invalidation: Triggered CloudFront distribution invalidation to purge edge caches
  • Verification: Live curl test against production domain confirmed delivery

Technical Architecture & Implementation Details

Static Site Generation & Deployment Pipeline

The Queen of San Diego website uses a Git-driven static site deployment model:

  • Source: Local filesystem repository at /Users/cb/Documents/repos/sites/queenofsandiego.com/
  • Storage: AWS S3 bucket (name stored as environment variable in .secrets/repos.env)
  • CDN: CloudFront distribution (ID also environment-sourced)
  • Deploy Script: publish_static_site.sh orchestrates the upload via AWS CLI

This pattern avoids application servers entirely — all content is pre-rendered HTML served directly from S3 with CloudFront caching. This reduces operational complexity and latency.

Deployment Commands

The deployment workflow follows this sequence:

# 1. Source environment credentials (non-interactive)
set -a
source /Users/cb/Documents/repos/.secrets/repos.env
set +a

# 2. Upload proposal HTML to S3
aws s3 cp proposals/jada-charter-proposal-sue.html \
  s3://${S3_BUCKET_NAME}/proposals/jada-charter-proposal-sue.html \
  --content-type "text/html; charset=utf-8"

# 3. Invalidate CloudFront cache for this path
aws cloudfront create-invalidation \
  --distribution-id ${CLOUDFRONT_DIST_ID} \
  --paths "/proposals/jada-charter-proposal-sue.html"

# 4. Poll invalidation status
aws cloudfront get-invalidation \
  --distribution-id ${CLOUDFRONT_DIST_ID} \
  --id ${INVALIDATION_ID}

Why this approach? The set -a/set +a pattern exports all variables from the env file without exposing them in shell history. Using environment variables instead of hardcoded values enables multi-environment support (dev/staging/production) without code changes.

Content Type Handling

The --content-type flag is critical: it sets the Content-Type HTTP header that S3 serves. Without it, browsers might download the HTML as a file rather than rendering it. The charset specification ensures proper Unicode handling in the browser.

File Structure & Proposal Content

The HTML proposal includes three main sections:

  • Pricing Options — Two tiers:
    • Option A (recommended): 2-hour charter, Captain Sergio only — $750 flat
    • Option B: 3-hour charter, full crew — $1,575 flat
  • Legal Compliance Footer — References USCG licensing, EPA/MPRSA ash scattering regulations, and bareboat charter exclusions. This protects the business from liability and sets clear expectations.
  • Historical Context — Hollywood Golden Age references (Bogart, Bacall, Flynn, Wayne) tied to San Diego maritime history, adding cultural value to the offering.
  • Interactive Q&A Form — Bottom-of-page conversion funnel; submits to bookings@queenofsandiego.com

Design Decision: Limited to exactly two pricing options. This reduces decision fatigue — a well-documented UX principle where too many choices paralyze purchasing decisions. Option A is explicitly marked "recommended" to guide prospects toward the higher-margin offering while respecting budget-conscious alternatives.

Infrastructure & DNS Resolution

The deployment chain assumes this infrastructure (all names/IDs stored securely in .secrets/repos.env):

Domain: queenofsandiego.com
  ↓ (DNS via Route53 or external registrar)
CloudFront Distribution (ID: env variable)
  ↓ (Origin)
S3 Bucket (Name: env variable)
  └── /proposals/jada-charter-proposal-sue.html

Cache Invalidation Strategy: CloudFront invalidation is asynchronous. The create-invalidation call returns an invalidation ID; you must poll get-invalidation to confirm completion (typically 60–120 seconds). The verification step confirms this propagated:

curl -s "https://queenofsandiego.com/proposals/jada-charter-proposal-sue.html" \
  | grep -E "charter|proposal|price" \
  | head -5

This grepping for known keywords confirms the live version contains the expected content.

Key Decisions & Rationale

  • Static HTML over dynamic content: No database, no runtime — just pre-rendered files. Faster, cheaper, more resilient. Trade-off: changes require a re-deploy (acceptable for proposal documents).
  • S3 + CloudFront over traditional web server: Eliminates EC2 costs, patching burden, and scaling complexity. CloudFront's global edge locations ensure sub-100ms latency worldwide.
  • Environment variable injection: Keeps credentials out of version control. The .secrets/repos.env file is .gitignore-d and never committed.
  • Explicit content-type header: Prevents browser download/rendering issues; especially important for forms that need JavaScript interactivity.
  • Bare-boat legal language: Protects the charter operator. EPA/MPRSA regulations govern ash scattering at sea; this disclosure shifts responsibility to the client to verify compliance.

What's Next

  • Photo Integration: The proposal currently lacks hero imagery. Path forward: source high-resolution photos of the vessel and San Diego landmarks, optimize for web (WebP format, ~200KB max), and wire into the HTML via <img src="assets/images/..."&