Sergey Kopanev: you sleep — agents ship

Go Back
AMORE: Acquisition, Monetization, Onboarding, Retention, Engagement · Part 9

Reading Exit Without Words


A user deletes the app.

Apple sends you nothing. Google sends you nothing. There is no “user uninstalled” webhook. There never was.

So your scheduler keeps firing. Push after push into a device that no longer exists. Email after email asking them to come back to a thing they already threw away. You are talking to a ghost and paying for the privilege.

The user left without a word. The engine has to read the silence.

What AMORE Is

AMORE — Acquisition, Monetization, Onboarding, Retention, Engagement — is the retention engine I built for a B2C health app. Previous articles covered offer conflict, stateful offers across channels, state-matched pressure, and optimistic state. This one covers the hardest read in the whole system: knowing when someone is gone.

No Event Exists

I went looking for the uninstall callback. There isn’t one.

Apple does not tell you. Google does not tell you. The mobile OS treats deletion as the user’s private business, which it is. The closest thing you get is a dead push token, and that arrives late, sometimes days late.

So you cannot detect the moment. You can only assemble a probability from proxy signals, after the fact.

app.foreground         → device alive, app open
silent push delivered  → device alive, app installed
APNs 410 Unregistered  → token dead, app probably gone
FCM NotRegistered      → token dead, app probably gone
no onNewToken in weeks  → strong gone signal

Each one is weak alone. A dead token can be a permissions revoke. A foreground gap can be a vacation. Stack them and a shape appears.

The Confidence Score

The engine keeps one number per user: uninstall_confidence, 0 to 1. A weighted sum of days-without-foreground, consecutive push errors, and time-since-token-refresh.

The buckets:

< 0.30   active
0.30–0.70  at risk
0.70–0.95  uninstall suspected
> 0.95   uninstall confirmed

The escalation ladder feeds it:

3 days no open   → push
7 days           → push + email
14 days          → silent push probe
30 days          → churn_suspected
60 days          → uninstall_confirmed (heuristic)

Three days dark is not a uninstall. It is a Tuesday. People have lives. Treating a short gap as departure is how you panic-spam a user who was always coming back.

The Number That Mattered: 0.7

Here is the rule the whole article hangs on. Stop selling at 0.7.

When confidence crosses 0.7 — uninstall suspected — the engine kills the push pipeline. No more upsells. No more winback offers. Email stays on, because email is cheap and unobtrusive. Everything interruptive goes quiet.

My first instinct was the opposite. User slipping away? Push harder. Fire the aggressive winback. Drop the 50%. Do something.

That instinct is wrong, and the reason is an asymmetry.

The Asymmetry

Two ways to be wrong about a departing user. They are not equally expensive.

False positive — you decide they left, they actually didn’t. You go quiet on someone still on the fence. Cost: one missed touch. They are at 0.7, already barely engaged. The marginal upsell was never going to convert anyway. You lose a sliver of expected revenue.

False negative — you decide they’re staying, they actually left. You keep hammering. Push after push at a wall, or worse, at a phone where the app still sits unopened. Cost: you spend quota, you train the few real eyes left to read you as noise, and if they’re on the edge you provide the final shove out the door.

The math is lopsided. Going silent on a leaver loses almost nothing — they’re gone, there was no sale. Hammering a stayer burns trust on exactly the population you most need to keep.

So when uncertain, shut up. The cost of restraint is small. The cost of pressure is your base.

Lagging, Not Live

The uncomfortable truth: this is a lagging indicator.

The engine never learns “the user just deleted the app.” It learns “the user probably deleted the app about a week ago.” Confidence climbs slowly because every signal it depends on arrives slow. Token death lags. Foreground gaps need days to mean anything.

That is fine for what it’s for: suppression lists, churn analytics, not wasting push budget on the unreachable. It is useless for “they left, win them back right now.”

For live reaction you work upstream, before the silence hardens. A user at free-churned — 14 days quiet but token still alive — is still reachable. That is the window. Push there. Once confidence passes 0.7, the window is closed; you are talking to a ghost, and the only respectful move is to stop.

The Trade-Off

Cost: you build a whole inference layer for a thing the OS refuses to report. Weighted scoring, silent-push probes against rate limits, a calibration loop that compares confidence against actual restore rate — if returning users had high scores, your weights were too aggressive. And you accept that you are always reacting to a departure that already happened.

Benefit: you stop paying to message ghosts. You stop shoving wavering users out the door with pressure they read as desperation. You spend your finite attention budget on the people still listening.

Takeaway

Users do not announce their exit. You read it from silence — dead tokens, foreground gaps, bounced pushes — and you read it late.

When the read says “probably gone,” the cheap mistake is to believe it and go quiet. The expensive mistake is to disbelieve it and keep selling. Stop at 0.7. A false alarm costs you one skipped upsell. A false all-clear costs you the user.


Next: Second 28 of onboarding (coming soon).