Runo Product Analytics & Amplitude Instrumentation
A single tracking facade so the rest of the app never has to know which analytics backend it's hitting
Worked on a product analytics layer at Runo — Amplitude provider abstraction, event taxonomy, user/company traits, deduplication, and platform-aware tracking across auth, billing, CRM, and call workflows.
153+
Analytics-related commits
Web + Mobile
Deployment surface
How it evolved
2026-04
Ad hoc tracking calls audited
Events were scattered across feature code with no shared identity, dedupe, or platform-awareness model.
2026-05
ProductAnalytics facade built
AmplitudeProvider behind a single AnalyticsProvider contract, with structured user/company traits.
2026-05
Deduplication and platform targeting tuned
Time-windowed dedupe and AnalyticsTargetPlatform detection rolled out across web and mobile.
This case study describes patterns and architecture inferred from commit history at Runo, a private codebase. Business outcomes (retention lift, funnel conversion changes) are intentionally omitted because they aren’t independently verifiable from available source material. Claims here are phrased as “worked on” / “contributed to” rather than “led,” per the evidence policy in this site’s README.
Problem & Context
Before this work, analytics events at Runo were scattered across the codebase rather than expressed through one coherent system. Tracking calls were added ad hoc wherever a feature needed visibility, which made it hard to reason about event identity, user/company context, deduplication, or platform-specific behavior (web vs. mobile, sandbox vs. production).
Solution & Architecture
The work centered on building a ProductAnalytics abstraction over an AmplitudeProvider, so the rest of the
app could call a single tracking surface without knowing which backend (or environment) it was hitting.
flowchart LR
A[App event call sites] --> B[ProductAnalytics facade]
B --> C{Environment check}
C -->|production, logged in| D[AmplitudeProvider]
C -->|sandbox / not logged in| E[No-op / logged skip]
D --> F[Amplitude]
B --> G[CrmActivityAccumulator]
G --> H[(Hive local storage)] Key pieces:
- Provider abstraction —
AmplitudeProviderimplements anAnalyticsProviderinterface, so initialization, identify, track, and flush logic live behind one contract. - User/company traits — structured
ProductAnalyticsUserTraitsrather than ad hoc property maps, covering signup date, team size, and account-level metadata. - Deduplication — a time-windowed dedupe check (tuned over several iterations) to avoid double-counting events fired from overlapping code paths.
- Platform targeting —
AnalyticsTargetPlatformdetection so web and mobile contexts can diverge where needed (e.g., disabling page-view autocapture on web, enabling it selectively on mobile). - CRM activity accumulation —
CrmActivityAccumulator, backed by Hive, summarizes CRM activity locally before it’s reported, rather than firing one event per micro-action.
Key Decisions & Tradeoffs
Sandbox/non-production hosts intentionally skip Amplitude initialization, with an explicit
forceSandbox escape
hatch for testing — trading a small amount of debug friction for protecting production analytics data quality.
- Chose to centralize identification logic in
ProductAnalyticsUtilsrather than scatteringidentify()calls near each login/signup site, after observing duplicate identification attempts. - Increased the event deduplication window after follow-up commits suggested the original window was too short for catching the events it was meant to catch.
- Renamed
report_eventstoactivity_eventsand moved internal_analytics to product_analytics — a deliberate internal-naming cleanup that traded short-term churn for clearer long-term boundaries between “raw analytics events” and “CRM activity summaries.”
Implementation Details
Instrumentation was added across login/signup, payment success, billing flows, attendance, rechurn, templates,
allocation rules, CRM fields, search execution, detail views, and filters — using the same AppEvent shape so
new events follow an existing pattern rather than inventing local conventions each time.
Lessons Learned
- Analytics infrastructure is the kind of work that’s easy to defer because it touches everything and failures are subtle — but the deduplication and skip-logging additions only became necessary after shipping the first version and seeing noisy data in practice.
- Flush behavior needed to become asynchronous and tied to app lifecycle changes (not just timers) to avoid losing events on background/close.
[!WARNING] Earlier versions of this system had an auto-identification path that was later removed — a reminder that “automatically identify when possible” can quietly create duplicate or premature identify calls if not scoped carefully to login state.
Related topics
Keep exploring
Designing Hybrid Telephony for a Sales CRM
A deeper look at how cloud and GSM calling were unified into one hybrid telephony model at Runo, and what broke before the model existed.
Designing an Event Taxonomy for Amplitude
How to name events and properties so analytics stays usable as the product grows, instead of becoming hundreds of one-off strings.
Runo Hybrid Telephony — Cloud & GSM Calling
Contributed to hybrid telephony support at Runo — letting users call via cloud or GSM, with call-medium selection, virtual number assignment, and consistent reporting across logs, exports, and analytics.
Amplitude's Event Taxonomy Documentation
Amplitude's own guidance on event/property naming and taxonomy planning — used as a reference while building out the analytics layer described in the Runo Product Analytics project.