Building a Driver-First Invoice & Job Capture System on QuickDumpNow's Existing AWS Stack

Over the past development session, we extended QuickDumpNow's dashboard infrastructure to support a complete invoice-to-job workflow. This post covers the architectural decisions, infrastructure changes, and implementation patterns that enable Sergio (our primary driver) to capture new customer orders via text or business card photo, generate branded invoices, and automatically spawn trackable jobs—all without rebuilding the existing dashboard or payment pipeline.

What Was Done

We added five interconnected systems to the QuickDumpNow tech stack:

  • OCR Business Card Capture — A presigned S3 upload endpoint that pipes card images to Claude Haiku vision, extracting contact details and auto-populating customer forms.
  • Invoice Generator Lambda — A new POST /invoice endpoint that renders the canonical Matt Fode PDF template as HTML, stores JSON metadata, and generates downloadable PDFs.
  • Magic-Link Customer Portal — A public-facing, token-authenticated invoice view at https://quickdumpnow.com/i/{invoice_id} with Stripe payment integration and account signup.
  • Job Auto-Spawn — Invoice creation immediately creates a corresponding Job record with a tracking token, enabling /track/{token} URLs to work before dashboard review.
  • Driver Dashboard UI — A new "New Job" wizard tab in the existing dashboard with dual-mode entry (form fields or business card photo) and a line-item editor for pricing adjustments.

Technical Details: Lambda & Environment Setup

The Lambda function lives at /tmp/qdn-work/src/lambda_function.py (555 lines in the canonical source). Rather than overwrite this monolith, we extended it with new route handlers:

# New routes added to the existing Lambda dispatcher
POST /ocr-card          → claude.vision(presigned_s3_url) → json response
POST /invoice           → render template + store JSON + generate PDF
POST /account           → create or link customer record
POST /account/reset     → password reset flow
GET  /i/{invoice_id}    → magic-link portal (public)

Environment variables were set directly on the Lambda function (not in code) to keep secrets out of version control:

ANTHROPIC_API_KEY       # Claude Haiku for OCR
STRIPE_SECRET_KEY       # Payment processing
STRIPE_PUBLISHABLE_KEY  # Client-side token generation

These were deployed via AWS CLI after zipping the Lambda source, ensuring the function had credentials before any traffic hit it.

Infrastructure: S3 Buckets & CloudFront

Invoice data and PDFs live in two distinct S3 locations:

  • s3://dashboard.quickdumpnow.com/data/invoices.json — Metadata: invoice ID, customer details, line items, total, timestamp. This is read by the dashboard pending-invoices view.
  • s3://quickdumpnow.com/invoices/{invoice_id}.pdf — Binary PDFs, no public ACL. Served only via CloudFront with a signed URL or API Gateway presigned redirect.
  • s3://dashboard.quickdumpnow.com/data/customers.json — Account records for repeat customers who signed up via magic link.

The public magic-link portal at /i/{invoice_id} required a CloudFront function rewrite. A new Cloudflare Worker (or CloudFront function, depending on your CDN choice) maps incoming requests:

GET /i/{invoice_id}?t={magic_token}
  → Validate token in Lambda via POST /validate-invoice-token
  → Fetch invoice JSON from S3
  → Render HTML template client-side
  → Display Stripe pay button

This keeps the invoice PDF behind your S3 bucket (no public access) while still allowing customers to view and pay without logging in.

Key Decisions & Why

Reusing Existing Dashboard Infrastructure

The QDN dashboard already had a customer/job model with tiered pricing (A/B/C cities × size buckets) and photo capture scaffolding. Rather than build a separate "order entry" system, we extended the existing dashboard with a "New Job" wizard tab. This meant:

  • No new database schema — invoices map onto existing Job records.
  • Pricing logic reused — the same tier/size multipliers that drive dashboard estimates now power invoice line items.
  • Single source of truth — all customer data flows through data/customers.json.

Magic-Link Over Login

Sergio needs to forward invoices to customers via text/WhatsApp (SMS is blocked by Twilio; we're waiting for unblock). A magic-link design means customers never need a password to view or pay—just a unique token sent in the URL. The token is single-use and expires in 24 hours, balancing security with friction.

Behind the scenes, we store the token in data/invoice_tokens.json with a TTL. A Lambda function validates it on every request:

POST /validate-invoice-token
  Input: invoice_id, token
  → Check if token exists and not expired
  → Return 200 + invoice JSON or 401 Unauthorized

Invoice = Job Trigger

Once an invoice is created, the Lambda immediately spawns a Job record with status pending_invoice and a unique tracking_token. This means:

  • Customers can track their job immediately via /track/{tracking_token}, even before Sergio or CB reviews it in the dashboard.
  • Sergio sees the pending invoice in the "Today" tab's Pending Invoices section and can edit, re-send, or mark paid without touching the Job.
  • Once marked paid (via Stripe webhook or manual override), the Job transitions to scheduled and appears in the calendar.

PDF Generation Approach

Rather than use an external service (which costs money and adds latency), we:

  1. Render the invoice as HTML using the Matt Fode template (stored at /tmp/qdn-work/invoice-template.html).
  2. Inject JSON data (customer, line items, totals) into the template via simple string substitution.
  3. Use a headless browser Lambda layer (Chromium via aws-lambda-powertools) to generate a PDF.
  4. Store the binary at s3://quickdumpnow.com/invoices/{invoice_id}.pdf.

This is cheaper than a third-party API and keeps all data within your AWS account.

Workflow: From Business Card to Payment

  1. Sergio opens dashboard → clicks "New Job" tab → selects "Snap business card" or "Enter manually".
  2. Business card route: Sergio takes a photo → presigned upload to S3 → Lambda calls claude-3-5-haiku-20241022 vision API → extracts name, company, phone, email, address → auto-fills form fields.
  3. Sergio adds jobsite address(es) → system