Telecom

Telz: international calls with the price up front

Since 2023 we have been the Android team behind Telz, an app for low-cost international calls. We turned an open-source SIP client into a paid product with upfront pricing and top-ups.

Project facts

Client
Telz (Nettia, Finland)
Year
2026
Timeline
Since 2023, ongoing
Platforms
Android
Stack
Kotlin, VoIP / SIP, Compose

What made the difference

Telz has been selling international calls since 2012. Its Android app started life as a fork of Linphone, the open-source SIP client: a dependable calling engine inside an interface built for a generic softphone rather than for someone calling family in another country. Since 2023 our team has worked on the app next to the client's in-house lead, shipping features, keeping up with each Android release and moving the codebase to modern Kotlin without stopping the roadmap.

The brief in one line: turn a generic softphone into a calling product people pay for, keep releasing, and leave the proven SIP core alone.

Where we started

The Linphone base gave Telz reliable VoIP, but everything around the call was generic. The first minutes in the app said little about price, privacy or how top-ups work. The code mixed several generations of Android patterns, so even small changes took longer than they should.

What we built

The price before the call

When a user taps a contact, Telz resolves the number to a country and lays out the options: an internet call over Wi-Fi or mobile data, or a callback in which Telz rings both people. Each option shows its per-minute rate, and we added a line that tells how many minutes the current balance covers. The choice takes a second, and the bill holds no surprises.

An onboarding that answers the first questions

The new onboarding is written in Jetpack Compose. Three short screens cover what a first-time user wants to know: no registration beyond a phone number, top-ups in any currency, and call recordings that stay on the device. A rates sheet with a currency switcher is one tap away, and a short data-privacy note appears before sign-in.

Payments and growth

We hardened the Google Play purchase flow against failed product loads and empty catalogues, built payment settings with auto top-up, and added an invite-a-friend bonus with promo codes. Install referrers and validated purchases go to AppsFlyer, so marketing can see which channels bring paying users.

Keeping pace with Android

Every Android release touches something a calling app relies on. We moved the app to edge-to-edge layouts for Android 15, prepared it for 16 KB memory pages, moved Linphone work onto its own core thread, added native in-app updates and a network observer, and worked through crash and ANR reports one by one.

Modernising without a rewrite

A full rewrite would have frozen the roadmap for months. We replaced the foundations one layer at a time, so each release still carried product work.

AreaWhat we didWhy it matters
Dependency injectionHilt across the app and the Linphone integrationOne home for singletons, testable components
PresentationMVI-style view models with state, events and one-off effectsPredictable screens, no lost navigation events
New UIJetpack Compose inside the existing fragment navigationNew screens ship without touching old ones
NetworkingCoroutines and a typed result wrapper, legacy RxJava calls keptEndpoints migrate one at a time
Phone numbersOwn country picker and validator on libphonenumberCorrect E.164 numbers mean correct rates
QualityRobot Framework and Appium tests on real Kobiton devices via GitHub ActionsRegressions show up on real hardware before release

One app, 36 locales

Telz users call across borders, so the interface speaks their languages, including Arabic, Hebrew and Persian, which read right to left. A translation check flags missing and inconsistent strings before a release goes out.

What the client has today


Running an Android product on an open-source core that needs to grow into something customers pay for? We would be glad to look at it with you.