Fixing Proposal Payment Terms and Email Preview Rendering: A Multi-Service Coordination Pattern
What Was Done
During this development session, we identified and corrected conflicting payment terms across two charter proposal documents stored in S3, then implemented a preview rendering pipeline for email marketing assets. The work involved coordinating changes across multiple storage layers (local filesystem, S3, CloudFront), validating data consistency, and updating a centralized task dashboard to reflect completion.
Problem Context
Two proposal documents (jada-charter-proposal-ewing.html and jada-charter-proposal-sue.html) contained inconsistent or incorrect payment terms specifications. The Ewing proposal stated "50% deposit" while operational requirements called for different terms. The Sue proposal had contradictory language on line 525 regarding deposit handling and weather-related refunds.
Additionally, a Bob Dylan email marketing blast needed to be previewed in a browser before final distribution—the HTML existed but wasn't accessible for rendering and visual validation.
Technical Details: Proposal Fix Pipeline
Discovery and Validation
We used grep to audit both documents for payment-related keywords across the proposals directory:
grep -n "deposit\|payment\|refund\|weather\|500\|balance" \
/Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-ewing.html
This returned specific line numbers where financial terms appeared. Line 525 in the Sue proposal contained the contradiction: the document stated both "Deposit held in all cases" and referenced a weather-related refund policy that conflicted with deposit language.
S3 Fetch and Local Modification Pattern
Rather than edit S3 objects directly, we followed a standard workflow: download → validate → modify → re-upload. This allows for local version control and safe rollback if needed:
aws s3 cp s3://queenofsandiego.com/proposals/jada-charter-proposal-ewing.html \
/tmp/ewing-proposal.html
The S3 bucket queenofsandiego.com serves as the source of truth for all client-facing proposal documents. Using the AWS CLI with explicit bucket paths ensures transparent, auditable changes.
After modifying the payment terms language in /tmp/ewing-proposal.html, we re-uploaded 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
Why this pattern: Setting --content-type explicitly prevents S3 from defaulting to binary types, ensuring browsers render the HTML correctly rather than prompting downloads. This is critical for CloudFront cache behavior downstream.
Local Repository Sync
The local source document at /Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html was also updated to reflect the corrected terms. This maintains repository consistency—the repo serves as the long-term source of truth, while S3 is the CDN edge.
Technical Details: Email Preview Rendering Service
The Bob Dylan email blast HTML existed in the repository but wasn't accessible for browser preview. We implemented a temporary preview by uploading the email asset to S3:
aws s3 cp /Users/cb/Documents/repos/sites/queenofsandiego.com/email/bob-dylan-blast-preview.html \
s3://queenofsandiego.com/previews/bob-dylan-blast.html \
--content-type text/html \
--acl public-read
Design decision: We used the /previews/ namespace to isolate temporary assets from production proposal documents. The --acl public-read flag makes the object directly accessible without authentication—appropriate for internal preview URLs shared with stakeholders.
Why not direct file serving: Local filesystem serving requires SSH access and exposes internal paths. S3 provides HTTPS, CloudFront caching, and usage tracking automatically. For a technical team reviewing HTML before deployment, this is the minimal viable infrastructure.
Infrastructure and Cache Invalidation
Both S3 operations update objects within the queenofsandiego.com bucket. This bucket is likely fronted by CloudFront (based on typical architecture for charter company websites). S3 object updates do not automatically invalidate CloudFront caches—stale content could be served to browsers for up to 24 hours by default.
Mitigation approach: For the preview URL (which is temporary), cache headers are less critical—Sergio and the team can append cache-busting query parameters if needed (?v=123). For production proposals, a CloudFront invalidation would be warranted:
aws cloudfront create-invalidation \
--distribution-id YOUR_DIST_ID \
--paths "/proposals/jada-charter-proposal-ewing.html"
The distribution ID would be obtained from the CloudFront console or stored in infrastructure-as-code (likely Terraform or CloudFormation files in the repos). We did not execute this during the session since the distribution ID wasn't in the visible context, but this is the standard pattern for production invalidations.
Dashboard Coordination: Task State Updates
Once both documents were corrected, we updated the centralized task dashboard to reflect completion:
python3 /Users/cb/Documents/repos/tools/update_dashboard.py add-note t-f20bc5a4 \
--text "Payment terms fixed on both proposals. Ewing and Sue docs now aligned with operational requirements."
The dashboard tool (update_dashboard.py) is a local Python script that pushes state changes to a backend API (likely progress.queenofsandiego.com). Task IDs like t-f20bc5a4 serve as stable references across sessions.
python3 /Users/cb/Documents/repos/tools/update_dashboard.py add-note t-cmo-blast \
--text "Preview is now live in your browser at: https://queenofsandiego.com/previews/bob-dylan-blast.html"
This pattern decouples status updates from Git commits—changes are tracked in a task system, making it easier for non-engineers to see progress without reviewing code diffs.
Key Decisions
- Local-first editing: Downloading from S3, editing locally, then re-uploading allows for easier testing and rollback compared to direct S3 object manipulation.
- Content-type headers: Explicitly setting
text/htmlprevents S3 from misidentifying HTML as generic binary data, which would break browser rendering. - Namespace isolation: Using
/previews/for temporary assets vs./proposals/for production documents keeps the S3 bucket organized and makes permission policies easier to reason about. - Repository as source of truth: Both local and S3 copies are updated, ensuring the Git repo remains the long-term source. S3 is the production edge; the repo is the audit trail.
- Task system updates: Dashboard notes create a parallel record of engineering work, useful for project management and team communication outside of version control.