The app is live. You described it, an agent built it, you clicked through the preview and it worked. Signups work. Checkout works. The nightly job that emails digests works, because you watched it run once.
That preview window is the most reassuring thing in software, and it can show you exactly one thing: what happens when you are looking.
The three bugs a preview cannot show you
The swallowed error. Agents are diligent about try/catch. Every route handler, every
server action, every webhook receiver gets one. Most of those catch blocks log to the console
of a server you will never open, then return something reasonable so the page does not crash. A
customer whose export failed sees a spinner that eventually stops. You see nothing.
The webhook that stopped verifying. You rotate a secret. The payment provider's new signing secret lands everywhere except production. From that moment every incoming webhook fails verification, the provider retries politely for a while, and customers keep paying for plans that never activate. The handler is not crashing. It is correctly rejecting every request.
The job that never ran. A cron string is subtly wrong after a refactor, or a queue worker is waiting on a call that never returns. Nothing throws, because nothing executes. The first person to notice is a customer asking why the digests stopped two weeks ago.
In all three cases the app is not erroring in any way that reaches you. The catch block ate it, the rejection was correct, the job simply did not happen.
Instrument the failure paths, not just the wins
The fix is old and unglamorous: the moments that matter in your app need to be events, sent
server-side, to something that will get them in front of a person. Signups and payments are the
obvious ones, and they are the pleasant half. The half that pays for itself is the failure path:
every catch, every rejected webhook, every job that finished with an error or did not finish.
You do not have to write that instrumentation. The agent that built the app can add it in one pass, if you tell it what good looks like. That is what the EventSend prompt is: one page of instructions that asks the agent to read the codebase, list the moments that matter in this product, write a small server-side helper that never throws and never blocks a response, and call it on every success and failure path it finds. There is a version for Cursor, Claude Code, Codex, Lovable, Bolt, v0 and a few others, each with a note about where a secret lives in that stack.
The helper is one HTTP POST:
await fetch("https://eventsend.io/api/events", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.EVENTSEND_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
event: "webhook-failed",
message: "Stripe webhook rejected: signature mismatch",
level: "critical",
icon: "❌",
payload: { source: "stripe", endpoint: "/api/webhooks/stripe" },
}),
});
Then route the critical ones to your pocket
Instrumentation only helps if the right event reaches the right place. This is the part a notification router does that a logging call cannot.
An event arrives on a route, flows through every topic that route feeds, and each topic's rules decide which destinations get it. The rules are a minimum level and an event-name pattern, set in the dashboard, so the split lives outside the code:
- Everything to a Slack or Discord channel, so the team has the feed.
errorand above to Telegram.criticalonly to a personal push destination: Pushover, or the browser push on your phone.
The rejected-webhook event above is critical, so it lands on your phone Friday at 6:40 PM, not
in a support inbox on Monday. The swallowed export error is error, so it lands in Telegram,
where you will see it before the customer writes in. The two hundred payment-failed events
during a provider outage are a burst, so a rollup on the channel delivers three of them and
one summary, and a threshold alert fires one critical when the count crosses twenty in two
minutes. And quiet hours hold everything below critical off your phone overnight, in your
own time zone.
Every attempt to every destination is in the delivery log, with the exact error when it failed. Retries are the router's job, on a fixed schedule, per destination. Your helper stays fire-and-forget.
What you will actually see
Once the prompt has run, the feed reads like the app's own diary, in its own vocabulary:
- 🎉
user-signup— New user signed up: [email protected] - 💰
payment-received— Payment received: 49.00 USD from [email protected] - ❌
export-failed— Export failed for [email protected]: timeout after 30s - ❌
webhook-failed— Stripe webhook rejected: signature mismatch
The first two are the reason you built the thing. The last two are the reason you will keep it running. A builder that ships features you can see is a wonderful tool. The part it cannot show you is the part that needs a channel, a level, and a phone that buzzes.
The free plan includes 2,500 events a month, three team destinations and one source. Paste the prompt, create a topic, and find out what your app has been trying to tell you.