🦊 FoxyInvoice

Chapter 10 — Feedback → tickets → shipped: closing the loop while they remember

The single most valuable object in this company is a bug report from a real user. It is worth more than a roadmap session (it's evidence, not opinion) and more than an analytics dashboard (it comes with intent and context). Everything in this chapter exists to shorten the distance between user notices problem and user hears it's fixed — because a loop that closes while they still remember filing it converts a critic into a collaborator.

flowchart TD
    USER["User hits a problem
(or has an idea)"] --> WIDGET["Floating 'Send feedback' widget
message + screenshot OR screen recording ≤15MB
anonymous OK (optional reply-to email)"] WIDGET --> API["POST /api/v1/support/feedback
(IP rate-limited)"] API --> STORE["Store + upload attachment to S3
(comment survives even if upload fails)"] API --> OUTBOX["Queue outbox email → founder
Reply-To = the reporter"] OUTBOX --> WORKER["Worker dispatches via SES"] WORKER --> INBOX["Founder inbox
(one glance, no login needed)"] STORE --> CONSOLE["Feedback console /feedback
New → Triaged → Resolved / Archived"] INBOX --> TRIAGE{"Daily triage
(part of the 20-min cadence)"} TRIAGE -->|bug| REPRO["Reproduce locally
(docker + seeded DB)"] TRIAGE -->|idea| BACKLOG["Backlog / roadmap"] REPRO --> FIX["Fix + test
commit with the feedback id"] FIX --> DEPLOY["Push to main → pipeline → live"] DEPLOY --> RESOLVE["Mark Resolved in console"] RESOLVE --> REPLY["Reply to reporter
(Reply-To makes this one click)"] REPLY --> USER BACKLOG -.->|scheduled| FIX

The widget: lower the friction to zero

A floating button on every page. No login required — anonymous users leave an optional reply-to email (and many do). The message field asks for anything: bugs, confusion, ideas. The attachment field accepts screenshots and screen recordings (MP4/WebM/MOV, ≤ 15 MB) — recordings after we learned that users describing a broken flow in words produce ambiguous prose, while thirty seconds of screen video is a repro script. Two small decisions that matter:

The notification: reply-able by construction

Submission queues an outbox email (Chapter 03's transactional pattern) to the founder with everything inline and — the detail that took a bounce-storm to learn — Reply-To set to the reporter's address. Staff hitting "reply" answers the user, not a dead mailbox. The email also carries a deep link straight to the console entry.

The console: a tiny ticket system, honestly scoped

A single page with four tabs — New → Triaged → Resolved / Archived — attachments rendered inline (with an auth-header blob fetch, because <img src> can't carry tokens), recordings playable in a <video> player, and a status workflow with timestamps. That's the entire ticket system. No SLAs, no assignee matrices, no Jira — a workflow is only as wide as the team that maintains it, and this one is maintained in the same codebase as the product (fix-adjacent, therefore fix-adjacent in the diff).

Case study: one bug, report to resolved

The best way to show the loop is a real one. A user reported: "On clicking Edit invoice button, new invoice page is opened. Not able to edit invoices." Same morning, a second report from the same user: double-clicking Create made duplicate invoices.

Repro (docker + seeded DB): both reproduced instantly. Diagnosis went two layers deep — the visible bug was Angular route-input timing (the editor read its id before the router bound it), but reproduction exposed something worse underneath: the invoice repository loaded entities without line items, so line updates 404'd and — the corruption class — adding a line recomputed totals against an empty set, silently wiping the invoice's math. One user's confused click had uncovered a data-integrity hole. Fix: an effect for the route input, and Include(LineItems) at the repository level (which also fixed quote conversion losing its lines). Ship: tests, commit referencing the feedback, push — the Chapter 07 pipeline had it live same-day. Close: status → Resolved; the reply (one click, thanks to Reply-To) told the user exactly what had been wrong and that their report caught a second bug they never saw. That user has filed more feedback than anyone — because the loop visibly worked.

Why this beats analytics

Analytics tells you that something is wrong ("drop-off on step 3"). Feedback tells you why ("there's no currency picker on this form" — a real report that same week). Instrument funnels, yes; but read your inbox like a scientist. At small scale, ten verbose humans outperform any dashboard you can build.


Recap. Zero-friction capture with recordings, reply-able notifications, a ticket workflow exactly as wide as the team, and a loop fast enough that the reporter experiences the fix. The feedback loop isn't support — it's your highest-signal product process.

Next: Chapter 11 — Reach: SEO, AI crawlers, and being the machine-readable answer.