TL;DR: On a small product you do not need an observability platform, you need to find out about a problem before your users or your client do. My setup is the same on almost every build: a crash and error reporter on every client (mobile, Unity, web) tagged with app version and platform, structured logs on the backend with one ID that follows a request or a call through every step, a handful of alerts tied to things a customer would notice, and a check that fires when something that should happen stops happening. That last one catches more real problems than everything else combined, because the worst failures on small products are silent.
I have shipped mobile apps and a Unity platform at RAQTS, a cross-platform C# SDK at Geonode, AI products like LectureNotes AI, and a lot of voice agents wired into n8n and GoHighLevel. The stacks are different. The way they fail is not. Something breaks quietly, nobody notices for a few days, and the first report is an annoyed message from a customer that starts with "is this still working?"
This post is about closing that gap without a dedicated ops team.
What you are actually trying to catch
Before picking any tool, it helps to name the failure types, because each needs a different net.
- Loud failures. A crash, an unhandled exception, a 500 from the API. These are the easy ones. Any decent error reporter catches them.
- Wrong-but-working failures. The request succeeds and the result is wrong. A leaderboard shows a stale score. A booking lands on the wrong day. A summary is empty. Nothing throws.
- Silent failures. Something that should happen simply does not. The post-call webhook stops firing. The nightly job never ran. Signups drop to zero because the form broke on one browser. No error, because no code ran.
Most small teams set up the first net and stop. Most of the pain I have seen lives in the second and third.
Layer one: errors on every client, with context
Every place your code runs on someone else's device needs an error reporter: the React Native app, the Unity build, the web frontend. Sentry and Firebase Crashlytics are the common choices, and on a small product the choice matters much less than actually having one on every client from the first release.
What matters is the context attached to each error:
- App version and build number. On mobile you never have one version in the wild. When I covered supporting old app versions, this was the reason: a crash that only happens on a build from three months ago is a different problem from one in today's release.
- Platform and OS version. At Geonode, the same networking core ran on Windows, Android and iOS, and a failure on one OS usually had an OS-specific cause like background execution limits. Without the platform tag you are guessing.
- A user or session ID, not personal data. Enough to find the related backend logs, not enough to leak anything.
Unity deserves its own note. Unity builds swallow a lot: a null reference in one frame can log an exception and carry on with half a scene broken. I route Unity's log callbacks into the reporter so those exceptions are counted, not just printed to a console nobody reads on a device. On standalone headsets, which I worked on at Nystag, this matters even more, because nobody is looking at a log while wearing the device.
Layer two: backend logs you can follow
Backend logging goes wrong in two ways: too little, so you cannot reconstruct what happened, or too much unstructured text, so you cannot find anything.
My rule is one ID that follows the work. For a web or mobile request, it is a request ID created at the edge and passed to every downstream call. For a voice agent, it is the call ID from the voice platform. When Retell sends a call event to n8n, and n8n updates GoHighLevel and sends an SMS, every step logs that same call ID. When a client says "a caller at 2:14 never got their confirmation text", I can search one ID and see exactly which step stopped. I described that pipeline in connecting Retell to n8n; this is the operational side of it.
A few habits that keep logs useful:
- Structured fields, not sentences.
event=booking_created call_id=... status=okis searchable. "Booking went fine for the caller!" is not. - Log the decision, not just the outcome. If a workflow skipped sending an SMS because the number was a landline, log that it skipped and why. Otherwise a correct skip looks identical to a bug.
- No secrets and as little personal data as possible. Logs get shared, exported and kept. The same discipline from handling API keys on client projects applies here: if it would hurt in a screenshot, it should not be in a log line.
Layer three: a few alerts, tied to customers
The fastest way to make monitoring useless is to alert on everything. After a week of noise, everyone mutes the channel and the one real alert gets missed.
I keep the list short and tie each alert to something a customer would notice:
- Error rate jumps after a release. Not "any error", but a clear rise compared with the previous version. This is the alert that tells you to flip a kill switch or halt a staged rollout.
- A core action fails repeatedly. Login, checkout, booking, the main AI call. A single failure is noise; five in ten minutes is a problem.
- A third party starts failing. The LLM provider, the voice platform, the CRM. Your code is fine and the product is still broken, which is exactly what voice agent outages and failover is about.
- Spend spikes. Usage-billed APIs make cost a reliability signal. A sudden jump in model or telephony spend is either growth or a bug, and you want to know which today. I learned to watch this closely on an AI product where cost per user decides whether the business works.
Each alert needs one named person who receives it and knows the first step. An alert that goes to a shared channel "for visibility" is a log line with extra steps.
Layer four: alert on silence
This is the layer I would set up first if I could only pick one, and the one I almost never see on inherited projects.
Instead of asking "did something fail?", ask "did the thing that should happen, happen?" Concretely:
- Expected volume checks. A clinic's voice agent normally handles calls every weekday morning. If there are zero calls by 11am on a Tuesday, something is wrong upstream: the number forwarding, the carrier, the agent itself. No error will ever tell you that.
- Heartbeats for scheduled jobs. Every cron job or scheduled workflow pings a check when it finishes. If the ping does not arrive on time, alert. A job that crashes logs an error. A job that never starts logs nothing.
- Downstream arrival checks. If every completed call should create a CRM note, compare the counts daily. When calls and notes drift apart, the post-call automation has broken somewhere in the middle. That gap is the most common failure I wrote about in post-call automation failures.
- Synthetic checks. For anything critical, a scheduled test that does the real thing: loads the signup page, places a test call to the agent, hits the main API endpoint. The regression testing I do before a release becomes, in a smaller form, a check that runs every day after it.
Silence checks are cheap to build and they catch the class of problem that otherwise takes days to surface.
Catching wrong-but-working
Wrong results are the hardest, because they need a definition of "right". Two approaches work on small products.
Invariants in code. Some things should always be true: a score never decreases unless an admin changed it, a booking is never in the past, a summary is never empty. At RAQTS the backend owned leaderboard state precisely so there was one place to enforce rules like that. When an invariant is broken, log it as an error even though nothing crashed.
Sampling real output. For AI features, some wrongness only shows up when a person reads it. On LectureNotes AI the useful habit was reading a sample of real outputs regularly, the same way I test LLM output before changes. For voice agents, I read a handful of call summaries and transcripts every week after launch. It is not automated and it does not need to be. Ten minutes a week catches drift that no alert would.
What to set up, in order
If you are launching something small and have one day for this, this is the order I would use:
- An error reporter on every client, tagged with version, platform and an anonymous user ID.
- One correlation ID through the backend and every workflow, logged as a structured field.
- A heartbeat on every scheduled job and one expected-volume check on your core action.
- Three to five alerts tied to customer-visible problems, each with a named owner.
- A spend alert on every usage-billed API.
- A weekly fifteen-minute habit of reading real output and the error dashboard.
That is a day of work on a new project, and it is part of what I mean when I say software needs maintenance after launch. It is also one of the first things I check when inheriting a codebase: if nobody can tell me how they would find out the product broke last night, that goes to the top of the list.
The real goal
Monitoring on a small product is not about dashboards. It is about the order in which people find out. If you hear about a problem from an alert, you fix it and maybe nobody notices. If you hear about it from a customer, you fix it and also spend a week rebuilding trust. Every layer above exists to move problems from the second group into the first.
I build AI voice agents, mobile and Unity apps, and the backends behind them for startups and agencies in the US, UK and Europe, and I set them up so you find out about problems before your customers do. If you want that on a new build, or a review of how an existing product is monitored, read more about my background or book a call.