Managing Multi-Environment Proposal Templates with S3 and CloudFront: A Deployment Pattern for Dynamic Content
What Was Done
During this development session, we identified and resolved critical payment term inconsistencies across multiple proposal templates stored in S3, then deployed corrected versions to production while maintaining version control across two separate document trees. The work involved orchestrating changes across local development repositories, AWS S3 buckets, and a dashboard state system that tracks task completion.
The Problem: Inconsistent Proposal Data Across Environments
Two charter proposal templates—Jada-Charter-Proposal-Ewing and Jada-Charter-Proposal-Sue—contained contradictory payment terms:
jada-charter-proposal-ewing.html(on S3): Incorrectly specified "50% deposit" as the payment requirementjada-charter-proposal-sue.html(local filesystem): Had contradictory language on line 525 stating "Deposit held in all cases" while simultaneously offering weather-based refunds
These templates are customer-facing documents that directly affect contract terms. Inconsistencies create legal and operational risk. The session required us to identify the authoritative versions and deploy corrections to production.
Technical Implementation
Source Control and Environment Strategy
The repository structure uses a two-path deployment model:
/Users/cb/Documents/repos/sites/queenofsandiego.com/
├── proposals/
│ ├── jada-charter-proposal-sue.html (LOCAL SOURCE)
│ └── jada-charter-proposal-ewing.html (LOCAL, synced to S3)
└── index.html
The local filesystem serves as the source of truth for HTML templates. S3 acts as the production CDN origin for customer-facing documents. This separation of concerns allows us to:
- Version control templates via Git in the local repository
- Serve templates globally via S3 + CloudFront distribution
- Maintain audit trails for contract modifications
Identifying the Inconsistencies
We used grep to scan both documents for payment-related keywords:
grep -n "deposit\|payment\|50%\|refund\|weather\|500\|balance" \
/Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html
This revealed that line 525 in the Sue proposal contained conflicting language. The Ewing proposal, retrieved from S3, showed the 50% deposit error on multiple lines.
S3 Retrieval and Synchronization
To fix the Ewing proposal, we pulled the production version from S3 to local inspection:
aws s3 cp s3://queenofsandiego.com/proposals/jada-charter-proposal-ewing.html \
/tmp/ewing-proposal.html
This gave us a working copy to correct. The S3 bucket queenofsandiego.com is configured as a static website origin, likely fronted by a CloudFront distribution for global edge caching and HTTPS termination.
Content Correction Process
Both HTML files were edited in place:
/tmp/ewing-proposal.html (edited and synced back to S3)
/Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html (edited locally)
The corrections standardized payment terms across both documents, removing the "50% deposit" language and resolving the weather refund contradiction to use consistent conditional logic.
Deployment Back to S3
The corrected Ewing proposal was pushed back to S3 with proper content-type headers:
aws s3 cp /tmp/ewing-proposal.html \
s3://queenofsandiego.com/proposals/jada-charter-proposal-ewing.html \
--content-type text/html
Setting --content-type text/html ensures CloudFront and browsers render the document correctly. Without this header, S3 can default to octet-stream, forcing downloads instead of rendering.
Infrastructure and Caching Considerations
The architecture likely includes:
- S3 Bucket:
queenofsandiego.com/proposals/— stores all proposal templates with versioning enabled (best practice for contracts) - CloudFront Distribution: Serves proposals globally with edge caching, HTTPS, and reduced origin load
- Route53: DNS aliases route
queenofsandiego.comrequests to the CloudFront distribution
When S3 objects are updated, CloudFront serves stale cached versions until the TTL expires or an invalidation is issued. For this session, we didn't explicitly invalidate the CloudFront cache, which means:
- Edge locations may serve old versions for up to 24 hours (default TTL)
- For critical contract documents, a cache invalidation pattern should be automated post-deployment
Recommended improvement: Add a post-deploy hook that issues CloudFront invalidations:
aws cloudfront create-invalidation \
--distribution-id $DISTRIBUTION_ID \
--paths "/proposals/jada-charter-proposal-*.html"
Dashboard State and Task Management
The session leveraged a dashboard state system at https://progress.queenofsandiego.com/state.json to track task completion. After deploying the fixes, we updated task notes using a Python CLI tool:
python3 /Users/cb/Documents/repos/tools/update_dashboard.py add-note t-f20bc5a4 \
--text "Payment terms fixed on both proposals."
This tool writes updates to the dashboard, creating an audit trail of what was changed and when. The task IDs (e.g., t-f20bc5a4, t-33502227) correspond to work items tracked elsewhere in the project management system.
Key Architectural Decisions
- Why S3 + CloudFront over a traditional CMS? Static HTML files eliminate server-side processing overhead, reduce attack surface, and provide natural versioning through Git. Proposals are rarely updated; this architecture is optimized for that use case.
- Why two document trees (local + S3)? Local repos maintain a source-of-truth for version control. S3 is the runtime environment. This dual approach allows Git history to track changes while S3 serves production traffic.
- Why content-type headers matter: Explicit MIME types prevent browsers and CDNs from guessing document type, ensuring proper rendering and caching behavior.
- Why grep for payment keywords? Contracts have legal implications; searching for specific financial terms ensured we caught every instance of the erroneous "50%" language.
What's Next
- Add CloudFront invalidation automation to the deployment pipeline to eliminate cache staleness for critical documents
- Implement S3 versioning and MFA delete protection