```html

Deploying a Dynamic Proposal Page: From Missing File to CloudFront CDN

What Was Done

A charter proposal page for the Queen of San Diego website was created from scratch and deployed to production. The file /Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html existed only as a template reference but was never actually generated or published. This post documents the full lifecycle: file creation, S3 upload, CloudFront cache invalidation, and verification.

The Problem: Missing Production File

During development, a grep search across the proposals directory revealed the file was referenced in templates but never instantiated. The template system had a pattern matching jada-charter-proposal-*, but the actual HTML artifact didn't exist in the S3 bucket. A curl request to https://queenofsandiego.com/proposals/jada-charter-proposal-sue.html returned 404.

Technical Implementation

File Structure and Content Strategy

The proposal page consolidates multiple concerns into a single self-contained HTML document:

  • Pricing Options: Two clearly differentiated charter packages (Option A: 2-hour, $750; Option B: 3-hour full crew, $1,575) to minimize decision fatigue
  • Legal Compliance: Explicit disclaimers about USCG licensing and EPA/MPRSA ash scattering regulations
  • Brand Content: Historical context linking the vessel to classic Hollywood (Bogart, Bacall, Flynn, Wayne era)
  • CTA Form: Q&A contact form at page footer for conversion

The file uses semantic HTML5 markup with inline CSS for portability—critical for proposal documents that may be emailed or printed.

Email Handling and Contact Management

An earlier iteration of the page contained specific contact information tied to an individual. During development, this was replaced with a generic business email (bookings@queenofsandiego.com) routed through the main website's contact infrastructure. This decouples personnel changes from proposal maintenance and allows email forwarding rules to be managed at the application level rather than hardcoded in HTML.

Deployment Pipeline

Local Development Workflow


# 1. File creation in local repo
$ cd /Users/cb/Documents/repos/sites/queenofsandiego.com
$ cat > proposals/jada-charter-proposal-sue.html << 'EOF'
[HTML content]
EOF

# 2. Verification before upload
$ find proposals/ -type f | sort
proposals/jada-charter-proposal-sue.html

AWS Deployment

The deployment leverages two AWS services:

  • S3 Bucket: queenofsandiego.com (primary static asset store)
  • CloudFront Distribution: Caching layer with automatic invalidation on deploy

# Load environment variables from secrets store
$ set -a; source /Users/cb/Documents/repos/.secrets/repos.env; set +a

# Upload to S3 (single file)
$ 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"

# Verify upload
$ aws s3api head-object \
  --bucket queenofsandiego.com \
  --key proposals/jada-charter-proposal-sue.html

Cache Invalidation

S3 alone doesn't guarantee immediate edge delivery. CloudFront acts as the origin-facing cache layer, distributing content across global edge locations. Because this is a new file, the cache miss is automatic, but we still trigger an explicit invalidation to ensure the CloudFront metadata is updated immediately:


# Create invalidation batch
$ aws cloudfront create-invalidation \
  --distribution-id [DISTRIBUTION_ID] \
  --paths "/proposals/jada-charter-proposal-sue.html"

# Monitor invalidation status
$ aws cloudfront get-invalidation \
  --distribution-id [DISTRIBUTION_ID] \
  --id [INVALIDATION_ID]

The distribution ID is stored in the secrets environment file and loaded at deploy time. CloudFront typically completes invalidations within 60 seconds, after which the file is cached at all edge locations globally.

Architecture Decisions

Why Self-Contained HTML Over a CMS Template

This proposal is a static artifact, not a dynamic view. A proposal document should be:

  • Immutable: Once shared, the URL should resolve to the exact content the recipient saw
  • Portable: Easily emailed, printed, or archived without server-side dependencies
  • Fast: No database queries or template rendering on request

Inline CSS ensures the page renders identically in all contexts (email clients, browsers, PDF converters).

Why CloudFront Invalidation for New Files

While new files automatically miss the cache, explicit invalidation patterns ensure:

  • CloudFront metadata is immediately consistent across all edge locations
  • Any stale entries from previous failed deployments are cleared
  • Deployment scripts have deterministic, observable behavior

Email Abstraction Pattern

Hardcoding email addresses in static HTML creates maintenance debt. The pattern used here—generic business email with backend routing—allows:

  • Personnel changes without redeploying HTML
  • Email forwarding and filtering at application level
  • Audit trails on who actually handles inquiries
  • Easier GDPR/privacy compliance (no personal data in static files)

Verification Steps

After deployment, verification occurs at three levels:

  • S3 Head Check: Confirms object exists and has correct metadata
  • CloudFront Propagation: Validates invalidation is in progress
  • HTTP Live Request: curl -s https://queenofsandiego.com/proposals/jada-charter-proposal-sue.html | grep -E "[pattern]" confirms content is accessible and parseable

The live curl response confirmed HTML structure was intact and key sections (pricing, legal disclaimers, contact form) were present.

What's Next

Pending: Asset integration (hero images, vessel photos, QR codes) require local file paths. Once provided, these will be:

  • Copied to /Users/cb/Documents/repos/sites/queenofsandiego.com/assets/images/proposals/
  • Uploaded to S3 with appropriate cache headers
  • Referenced in HTML via relative or CDN paths
  • Validated for size (CloudFront has no size limits, but optimal is <100KB per image for web)

Monitoring: CloudFront distribution logs can be analyzed to track proposal page views and geographic distribution via standard S3 access logs enabled on the bucket.