Skip to content
Arrit Gashi
← All work
Shipped2026Mobile app

PulseApp CRM

Turns a mortgage or real-estate professional's contact book into a ranked daily call queue.

The PulseApp marketing site hero: the headline The mortgage CRM where your contacts stay yours, beside a phone showing the app's Analytics screen with weekly call counts and a disposition breakdown.
The marketing site at mobile-crm-app.com, with the Analytics screen in frame.
subscription tiers across 3 roles
9
languages at structural parity
3
third-party analytics or crash SDKs
0

The problem

Wholesale account executives, loan officers and realtors live on the phone but work their book from a spreadsheet, deciding who to call next by scrolling and guessing. The reply I wrote to App Review put it plainly: high-value contacts go uncalled for weeks while low-value ones get called twice. The CRMs that solve this are desktop, team-oriented, and require handing an entire private contact book to a vendor who can read every row of it.

What I built

One iOS app that ships as three products. Account executive, loan officer and realtor each get their own fields, their own scoring weights and their own price ladder, chosen at signup and pinned by row-level security — but they run on one engine. The loop is deliberately small: open the queue, tap to call, log the outcome in two taps, and the follow-up schedules itself. Every path that completes a call routes through a single function, so a shortcut flow cannot drift away from the full one.

Privacy is the architecture rather than a feature. A random master key encrypts all contact information on the device, and that key is sealed into two separate lock boxes — one opened by the login password, one by a sixteen-character recovery key. The operator holds only ciphertext. Phone numbers are matched for duplicates through an HMAC fingerprint, so deduplication works without the server ever seeing a number.

There is no third-party analytics, advertising, attribution or crash SDK anywhere in it. Crash reporting is self-hosted into the project's own database, specifically so the privacy policy can say there are no third-party analytics or crash-reporting services with no carve-out underneath it.

The hard part

Four copies of one contact book

Four tab screens each held their own decrypted copy of the entire contact book, and each ran its own realtime subscription that re-downloaded the whole table whenever anything changed. Tab screens never unmount, so a single row update — which every logged call performs — cost four full downloads and four decrypt passes. For a user with two thousand contacts making a hundred calls a day, that projected to roughly a gigabyte of egress per day: a wall that arrives at a few dozen heavy users, not a few thousand.

The fix was one fetch, one subscription, and realtime payloads applied as deltas. The more interesting decision was what deliberately stayed a full refetch: server-side deletes, because a filtered subscription never emits a matching delete event; returning to the foreground; a subscription that reconnected after dropping; and any delta that fails to apply. The same pass caught a latent bug all four fetchers shared — a failed read was writing an empty array into state, silently emptying the book. The rule that came out of it is that a failed read must never write state, and a partial page counts as a failure, because a truncated book must not be able to masquerade as a complete one.

Outcome

  • Live on the US App Store, free with nine annual subscription tiers across three professional roles.
  • Cleared three separate App Review rounds — an information request, a metadata rejection, and an in-app-purchase failure.
  • The purchase failure was root-caused entirely server-side and fixed with no new binary: the build under review never had to be replaced.
  • English, Spanish and Chinese shipped at strict structural parity, enforced by verification scripts that run before every commit.

Screens

  • The Queue screen: eighteen contacts sorted by priority score, filter chips for tier and time zone, a notice that three contacts are outside calling hours, and a Start Queue button.

    The ranked call queue. Priority score decides who is next; contacts outside calling hours drop to the end.

  • The Call Disposition screen after a call: seven outcome buttons with Spoke, Qualified selected, a one-week follow-up, an approximate call duration, and a notes field.

    Logging a call: one outcome, one follow-up interval, save and next. Every path that completes a call goes through the same function.

  • The Create Account screen asking what best describes you, with Account Executive selected over Loan Officer and Realtor, and name fields below.

    Three products on one engine: the role chosen at signup sets the fields, the scoring weights and the price ladder.

  • The Save Your Recovery Key screen: a sixteen-character key in a highlighted box, a copy-to-clipboard button, and a warning that it is the only way to recover data if the password is forgotten.

    The recovery key opens the second lock box on the master key. The operator never holds either.

  • The Upgrade screen: Basic at $199 a year for up to 100 contacts, and Pro, marked most popular, at $299 a year for up to 400.

    Annual tiers by contact volume. Every plan includes every feature.

Want something like this built? I take on a small number of projects at a time.