Tyagi // Engineering Log
← Back to Case Studies
Case Study production

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.

Date 2026-04
Reading Time 2 min read
Difficulty
Advanced
Stack
Flutter,Dart,Telephony
Reported

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.

Diagram source (mermaid)
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.

See the underlying project entry — Runo Hybrid Telephony — for the full list of surfaces this touched.