Deploying a Job Dispatch Workflow: CloudFront Rewrites, S3 State Management, and Multi-Stage Promotion

This post covers the technical approach taken to deploy QuickDumpNow's dispatch tool to production, including infrastructure decisions around CloudFront function rewrites, S3-backed job state management, and parallel deployment patterns.

What We Built

The QuickDumpNow dispatch system needed to:

  • Accept new job creation requests via a booking interface
  • Track job status through a customer-facing tracking page
  • Maintain a source of truth for job state in S3
  • Route traffic to the correct frontend based on URL patterns
  • Support rapid iteration with staged deployments before promotion to production

The specific deployment task involved pushing the dashboard, tracking page, and booking pages to production, while also creating a new job for Mark at a Soderblom Ave location with a $600 flat-rate agreement.

CloudFront Function Rewrites: The URL Routing Layer

The core infrastructure challenge was handling URL routing without backend server logic. We solved this with a CloudFront function that rewrites requests based on path patterns.

File: /Users/cb/Documents/repos/sites/quickdumpnow.com/cf/qdn-track-rewrite.js

The function was updated to handle two distinct patterns:

  • /track/* → Rewrites to /track.html for customer tracking pages
  • /book/* → Rewrites to /book.html for booking/job creation pages

The rewrite strategy preserves query strings and uses CloudFront's request-time function execution—this happens before origin requests, keeping latency minimal and avoiding extra round trips to S3.

// Example rewrite pattern (actual credentials/implementation omitted)
if (request.uri.startsWith('/track/')) {
  request.uri = '/track.html';
}
if (request.uri.startsWith('/book/')) {
  request.uri = '/book.html';
}
return request;

Why this approach? S3 doesn't support dynamic routing or conditional rewrites natively. CloudFront functions execute at edge locations with minimal overhead, keeping request latency under 5ms. The alternative—Lambda@Edge—would add 20-50ms of cold-start latency and higher operational complexity. For a dispatch tool serving time-sensitive requests, this matters.

S3-Based Job State Management

Rather than maintaining a database, jobs are stored as JSON documents in S3. This approach trades some query flexibility for simplicity and cost.

Location: s3://qdn-jobs-production/jobs.json

Each job entry includes:

  • Job ID: Unique identifier (generated as UUID or sequential)
  • Tracking token: Cryptographically random, URL-safe string for customer-facing tracking links
  • Location: Street address and service details
  • Status: One of: booked, ready_for_pickup, in_progress, completed
  • Pricing: Flat rate ($600 for Mark) or itemized breakdown
  • Metadata: Creation timestamp, last updated, assigned crew member

The workflow for creating Mark's job:

  1. Generate a unique job ID and 32-character tracking token
  2. Build the job JSON object with initial status booked
  3. Fetch current jobs.json from S3 (retrieve ETag for conditional updates)
  4. Append the new job entry
  5. Upload back to S3 with the ETag header to prevent race conditions
  6. Once Mark confirms readiness, update the job status to ready_for_pickup

Why S3 for state? For a small dispatch operation with <100 jobs per day, S3 eliminates database provisioning and reduces operational overhead. ETags provide optimistic concurrency control. The trade-off is that complex queries (e.g., "all jobs by crew member") require reading the full file—acceptable given the scale. For future growth beyond 1000 jobs/day, migrating to DynamoDB would be straightforward.

Multi-Stage Deployment Pattern

The deployment uses three CloudFront distributions and explicit promotion stages to reduce production incidents:

  • Staging distribution: Internal testing, feature branches, QA validation
  • Production distribution: Customer-facing, cached globally
  • Dashboard (internal): Crew and dispatch personnel use a separate distribution with authentication

Files were promoted in sequence:

# Pseudocode; actual CLI tools used
1. Publish CF function to LIVE stage
   - Updates qdn-track-rewrite.js with /book/ rewrite logic
   - Retrieves ETag before update for atomic operations
   
2. Promote staged dashboard to prod
   - File: /dashboard.html → production CloudFront distribution
   
3. Promote staging track page to prod
   - File: /track.html → production distribution
   
4. Promote staging booking pages to prod
   - File: /book.html → production distribution
   
5. Invalidate CloudFront cache
   - Invalidate /track/* and /book/* paths
   - Distribute ID for quickdumpnow.com: (not disclosed)
   - Distributing ID for dashboard: (not disclosed)

Cache invalidations were critical: without them, CloudFront would serve stale HTML for up to 24 hours (default TTL). We invalidated specific paths rather than wildcard invalidations to avoid purging unrelated assets.

The Tracking Link Problem

The initial issue—"Job not found" on the tracking page—occurred because:

  1. The tracking page's JavaScript fetches jobs.json from S3 (via CloudFront)
  2. It searches for a matching tracking token in the JSON array
  3. Mark's job didn't exist yet in production jobs.json

The fix was simple: insert Mark's job into jobs.json and invalidate the CloudFront cache serving that asset. The tracking page's JavaScript would then find the token and display status information.

Frontend implementation sketch (simplified):

// In /track.html, a service worker or fetch call reads:
fetch('https://cdn.quickdumpnow.com/jobs.json')
  .then(r => r.json())
  .then(jobs => {
    const token = new URLSearchParams(window.location.search).get('token');
    const job = jobs.find(j => j.tracking_token === token);
    if (job) {
      renderJobStatus(job);
    } else {
      showError('Job not found, or this tracking link is no longer valid');
    }
  });

Infrastructure Summary

  • S3 buckets: qdn-jobs-production (job state), qdn-frontend-prod (HTML/assets)
  • CloudF