Somebody just noticed your product is broken. You have about ten seconds.
The toast disappears. The failed request is gone. The state scrolls away. Relay is one button that captures all of it at that moment, scrubs it on the server, and hands a brief to a person or a coding agent.
What actually happens on a press
- The screen is photographed, without asking
- The page rasterises its own DOM, expanding every scroll pane first, so the picture is the whole view rather than the part that fit on the glass. On a phone, asking for a screenshot means a button combination, a photo library and a picker — and by the time they are back, the thing they wanted to report has scrolled off. A screenshot that is a chore produces reports without one.
- The failing request is already in hand
- A small recorder installs before your own code and keeps a bounded ring buffer of requests, uncaught errors, your console errors, route changes and what was pressed in order. You cannot capture the past, and the failing call is usually the bug. It never records request or response bodies.
- Everything else is extracted
- Alerts, the conversation as rendered, tables as rows, forms with their values, repeated records, editors, open dialogs and whether their buttons were disabled, plus a greppable DOM outline. Password fields are photographed empty at source — never captured, not merely redacted.
- It is scrubbed on the server
- Before the first write, because a browser can be edited and the copy on disk is the one that leaks. The principle is keep the shape, drop the identity: a defect lives in the shape, and the shape survives dana@acme.com becoming <email 1>. Ids, selectors and build stamps are never touched, because a redacted selector protects nobody and costs the whole report.
- The fixer gets a brief, not a dump
- What they said, what they pointed at, the exact commit they were running, the platform, what the screenshot is and is not, each region rendered as itself, and the failing requests flagged inline. Hand over JSON and somebody has to become an archaeologist before they can become an engineer.
Two script tags. Any stack.
No build step, no framework, no dependency. The split matters: the recorder has to run before your own code, because a request that failed before it parsed is a request nobody will ever see.
<script src="https://relay.example.com/relay-client/recorder.js"
data-relay-surface="your-app"
data-relay-endpoint="https://relay.example.com/api/relay/report"
data-relay-key="rlk_…"></script>
<script type="module" src="https://relay.example.com/relay-client/relay.js"></script>Then it goes where your team already works
- Cursor
- Launches a cloud agent on the repository with the brief as its prompt and the screenshot attached.
- Claude
- Writes a first pass at the diagnosis onto the report: the likely cause, the evidence for it, where to look, and what would disprove it.
- Linear or GitHub
- Opens an issue in your team, with a link back to the report and its picture.
- A webhook
- Signed JSON, for whatever you already run.
- Nowhere
- A real configuration. Reports are still captured and still read in the console.
Forty people, one bug, one issue
Reports are grouped by what the machine observed — the same uncaught error, the same failing call — so the fortieth report of one fault does not open a fortieth issue or send a fortieth notification. A report with no machine evidence is left ungrouped and says so, because grouping on the screen alone would hide distinct bugs behind each other.
What it will not do
It will not record request bodies. It will not capture a password field. It will not let a client decide what was redacted. It will not merge a pull request on green. And it will not refuse somebody’s report because their vendor is over a plan limit — the person who pressed the button is not the customer, and their evidence exists once.