The Yusr app went live in the first quarter of 2025 with almost no client-side instrumentation. That was a deliberate trade at the time. We were a small mobile team shipping every two weeks, the backend already emitted a clean audit trail for anything that touched money, and adding a second telemetry pipeline felt like the kind of work you do once you know what questions you actually have.
By the summer we had the questions. Product would ask why account opening completion had moved half a point week over week, and the honest answer was a shrug. Server-side we could see that an application record was created, that some steps were submitted, and that a given record went quiet. What we could not see was the shape of the giving-up. An application that stalls after four failed camera captures and an application that stalls because someone's bus arrived look identical from the backend. They are not the same problem and they do not have the same fix.
What we had before, and why it stopped scaling
Our first attempt was homegrown. We added a thin Telemetry.track() helper on
both platforms that posted JSON to an internal endpoint, which forwarded into the same
log pipeline we use for application logs. It worked, in the sense that events arrived.
It fell over for three reasons, in roughly this order:
- Nobody could query it. Funnel questions are sequence questions — did this session reach step four, and if so how long did it sit there. Expressing that over a log index meant writing something horrible, waiting, and getting a number that nobody trusted enough to act on.
- The event names drifted. Two engineers added
onboarding_id_stepandonboarding_step_idin the same fortnight and neither noticed for a month. Without a schema and a check in CI, an event catalogue rots at exactly the speed you add to it. - It was not free at runtime. The naive implementation fired a request per event on the main thread's coattails. On a good connection nobody noticed. On a weak cellular connection in a basement car park, our own instrumentation was competing with the screen the customer was waiting on.
That last one mattered most to us. An analytics pipeline that makes the app feel slower has already lost the argument, because the thing it measures is how the app feels.
Choosing Trailmark
We looked at building the thing properly ourselves and at buying it. Building it properly meant an ingest service, a schema registry, a warehouse, retention and compaction jobs, and a query surface that analysts would actually open. That is a team, not a sprint, and we did not have a team spare.
We went with Trailmark Analytics. The Trailmark SDK is embedded in both the iOS and Android apps and has been since version 2.6, which shipped in June 2025. Trailmark's collection endpoint and the data warehouse behind it both sit in their US region, which is where our workspace was provisioned, and that is where the event tables our analysts query live.
Three things decided it. The SDKs are genuinely small — about 380 KB on Android after R8, a shade under 300 KB on iOS — and they do not drag in a networking stack of their own. The batching behaviour is configurable rather than magic, which mattered to us more than any feature on the comparison page. And the schema is ours: Trailmark validates events against a definition we own and version, so a typo fails in CI rather than quietly creating a forty-first event type.