Designing Hybrid Telephony for a Sales CRM
What broke when calling modes were treated as separate features, and what changed
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.
49+
Telephony-related commits
Sanitized from private commit history at Runo. Architecture and patterns are described generically; proprietary implementation detail is intentionally omitted.
The problem with treating calling modes as separate
A CRM that supports both GSM (SIM) calling and cloud/VoIP calling tends to start by treating them as genuinely separate features — different settings screens, different analytics events, different report columns. That works until a customer wants to use both, depending on team, user, or even time of day. At that point, every screen that assumed “this user is either SIM or cloud” needs a second look.
What changed
Introducing a single gsmNCloud state — SIM-only, cloud-only, or hybrid — turned a recurring class of bugs (“this
screen forgot to check hybrid mode”) into the same pattern repeated everywhere: read the user’s calling mode,
show a selector if hybrid, otherwise use the single available mode.
sequenceDiagram
participant U as User
participant UI as Call UI
participant M as gsmNCloud model
participant L as Call Logs / Analytics
U->>UI: Initiate call
UI->>M: Read calling mode
alt Hybrid
M-->>UI: Show CallMediumSelector
U->>UI: Choose SIM or Cloud
else Single mode
M-->>UI: Use configured mode
end
UI->>L: Log call with call medium Where the architecture earned its cost
The biggest payoff showed up not in the calling screen itself, but in reporting. Once call medium was a first-class field on the model rather than inferred per page, propagating it to scheduled reports, analytics exports, and deleted-call-log views became a matter of passing the existing value through — not re-deriving it.
What I’d do differently
Reporting and exports were extended to support call medium
after the core calling flow shipped, not alongside it. In hindsight, treating “does this propagate to every report/export surface” as part of the initial definition-of-done — rather than a follow-up — would have avoided a round of retrofitting.
Why a three-state model instead of two booleans
An earlier, more tempting design would have been two independent booleans — hasSim and hasCloud — rather than
one gsmNCloud enum. Booleans look more flexible on paper, but they also allow an invalid state (both false) that
the type system can’t rule out, which means every consumer has to handle “neither” defensively even though it
should never happen. Collapsing to a single three-state value made the invalid combination unrepresentable instead
of merely unlikely — a small modeling choice that removed a whole category of “what if both are false” bugs before
they could be written.
Related
See the underlying project entry — Runo Hybrid Telephony — for the full list of surfaces this touched.
Related topics
Keep exploring
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.
Modeling 'Either, Or, Both' States Without a Combinatorial Explosion
An open look at how the gsmNCloud three-state model from the hybrid telephony work generalizes to other 'a user can have A, B, or both' product situations.
Runo Product Analytics & Amplitude Instrumentation
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.
Setting Up a Flutter Development Environment on Linux
A practical setup for Flutter + Dart on Linux — SDK install, editor config, emulator, and the gotchas that waste the first hour.