```html

Publishing Charter Documents to S3 with CloudFront Cache Invalidation: Quinn Male Case Study

This post documents the workflow for publishing dynamically-generated charter documents (manifests and trip sheets) to S3, invalidating CloudFront cache to ensure immediate availability, and maintaining document durability across multiple storage locations in the JADA operations infrastructure.

What Was Done

Over the course of a development session, we:

  • Generated two HTML documents for a charter booking (Quinn Male manifest and trip sheet)
  • Published these documents to two separate S3 prefixes for redundancy and organizational clarity
  • Invalidated CloudFront cache to ensure live URLs returned fresh content immediately
  • Verified content consistency across published URLs
  • Established a durable copy in the local jada-ops repository for version control

Technical Details: Document Generation and Storage Strategy

Charter documents were initially generated in /tmp as temporary HTML artifacts:

/tmp/quinn-male-manifest.html
/tmp/quinn-male-trip-sheet.html

These documents were then published to the S3 bucket backing the shipcaptaincrew infrastructure. The key architectural decision was to maintain two S3 prefixes for the same documents:

  • Primary location: s3://shipcaptaincrew-docs/manifests/quinn-male-manifest.html — Used by the event crew page to render downloadable documents for passengers and crew
  • Secondary location: s3://shipcaptaincrew-docs/crew-page/quinn-male-manifest.html — Maintained in the crew-page prefix for integration with the frontend SPA's document-handling logic

This dual-storage approach accommodates two different document-rendering code paths in the shipcaptaincrew codebase:

  • The Lambda function build_event_pages constructs download links pointing to the manifests prefix
  • The frontend SPA's document route handler looks up docs in the crew-page prefix

By publishing to both locations, we ensure that regardless of which code path is active or being refactored, the documents remain accessible.

Infrastructure: S3, CloudFront, and Cache Invalidation

The shipcaptaincrew infrastructure uses a CloudFront distribution fronting the S3 bucket. When documents are updated in S3, CloudFront's cache must be explicitly invalidated to prevent stale content from being served to end users. The distribution ID for shipcaptaincrew is referenced programmatically in the deployment tooling.

Cache invalidation was performed after each publish operation:

# Invalidate CloudFront cache for manifest
aws cloudfront create-invalidation --distribution-id [DISTRIBUTION_ID] --paths "/manifests/quinn-male-manifest.html"

# Invalidate CloudFront cache for crew-page prefix
aws cloudfront create-invalidation --distribution-id [DISTRIBUTION_ID] --paths "/crew-page/quinn-male-manifest.html"

The invalidation process creates a CloudFront invalidation request, which typically takes 60-90 seconds to propagate across CloudFront edge locations globally. Verification was performed by making HTTP requests to the CloudFront-fronted URLs and confirming HTTP 200 responses with correct content.

Document Durability: Local Repository Copy

In addition to S3 storage, both documents were copied to the local jada-ops repository:

/Users/cb/Documents/repos/jada-ops/quinn-male/quinn-male-manifest.html
/Users/cb/Documents/repos/jada-ops/quinn-male/quinn-male-trip-sheet.html

This serves as a durable, version-controlled copy that can be:

  • Referenced during charter execution for crew coordination
  • Included in git commits for audit trails
  • Republished to S3 if the temporary artifacts are lost
  • Manually inspected by operations staff without requiring S3 access

The local jada-ops directory is organized by charter identifier (quinn-male in this case), establishing a consistent pattern for future charters.

Key Decisions and Rationale

Why Two S3 Prefixes?

The shipcaptaincrew codebase evolved to support multiple document-rendering patterns. Rather than refactoring all code paths immediately, we chose to publish to both prefixes. This decision prioritizes operational continuity over code deduplication. If a document rendering code path were to fail, documents would still be retrievable from the alternate prefix. This is particularly important for production charters where crew coordination depends on document availability.

Why Local Repository Storage?

S3 is the source of truth for what's currently live on the public-facing crew pages. However, local repository storage creates a local-first operational model for crew members who may lack AWS access or prefer working with filesystem-based artifacts. It also establishes a pattern for future incident recovery: if S3 were compromised or documents were accidentally deleted, the local jada-ops directory would serve as a recovery point.

Content-Type Headers

When publishing HTML documents to S3, the Content-Type metadata was explicitly set to text/html; charset=utf-8. This ensures that browsers render the documents as HTML rather than offering them for download. This is critical for the crew page integration, where the SPA expects to receive renderable HTML.

Verification Workflow

After publishing and cache invalidation, verification followed this sequence:

  1. Spot-check HTTP status: Confirm that CloudFront-fronted URLs returned HTTP 200 (not 404 or 403)
  2. Content comparison: Download the published content and compare with the local artifact to ensure no corruption occurred during S3 upload
  3. Live rendering test: Verify that names and charter-specific details rendered correctly in the published HTML
  4. Multi-prefix verification: Confirm that both S3 prefixes returned identical content

What's Next

This workflow represents the manual, ad-hoc approach to charter document publishing. For higher operational velocity, the following automations would be beneficial:

  • Lambda-based document generation: Trigger document generation directly from calendar events, eliminating the manual /tmp creation step
  • Automated S3 publishing: Integrate document generation with S3 publish logic in a single Lambda function
  • Cache invalidation as part of publish: Automatically invalidate CloudFront after S3 writes, removing the need for explicit invalidation commands
  • Event-driven repository updates: Publish to jada-ops automatically via post-publish Lambda hooks

For now, this manual approach provides transparency into the publishing workflow and allows operational staff to verify each step before documents are made live to crew and passengers.

```