← Back to Blog
RetentionBuilder NotesAI CompanionProductData

I Looked at My AI Companion App's Retention Data. The Cliff Wasn't Where I Thought.

Tendera Team6 min read

The Dashboard Was Lying to Me

Before any of the numbers, the embarrassing part.

I have an admin dashboard for Tendera that shows retention, a message leaderboard, unique visitors, referrers. I'd been reading it for weeks. It was wrong.

Supabase — which is to say PostgREST underneath it — enforces a server-side cap on how many rows a single read returns. On this project that cap is 1000. Here's the part that got me: `.limit(50000)` does not override it. The response is silently clamped to 1000 rows. No error, no warning, no truncation flag. Whichever `.order()` you used decides which 1000 rows you get.

There was even a comment in my own code claiming a previous fix had solved this. It hadn't. It had added a bigger `.limit()`, which does nothing.

So once the messages table passed a thousand rows, every "read the whole table" query in that dashboard was quietly reading the newest thousand and calling it everything. Retention, the leaderboard, total visitors, the device split, the referrer breakdown — all computed on a truncated slice. The numbers looked plausible. They were fiction.

The fix is unglamorous: paginate with `.range(from, from + 999)` in a loop until a page comes back short. Don't trust `.limit()` for completeness. If you're building on Supabase and you have any query whose result you treat as "all the rows," go check it right now. This is the kind of bug that doesn't announce itself — it just quietly makes you wrong about your own product.

The reason I'm leading with this: everything below only exists because the real numbers turned out to be a different story than the fake ones.

The Real Numbers

Measured across all history, after the fix:

  • Activation: ~71%. Of people who register, roughly seven in ten send at least one message.
  • D1: ~15%. Of activated users mature enough to have had the chance, about one in seven came back a day or more later.
  • D7: ~13%.
  • D30: ~12%.
  • Two caveats before anyone quotes these at me. The cohorts are small — this is a young product, and these are rates over tens of people, not thousands. Treat them as directional. And they're anchored on a user's first message, not their signup date, so "activation" and "retention" are measuring different populations on purpose.

    The truncated dashboard, for what it's worth, had been showing me an activation rate around 38% and a D30 of zero. Both were artifacts. The newest thousand messages simply didn't contain the early heavy users, so the people who'd been around longest were invisible to my own analytics.

    The Shape Is the Finding, Not the Numbers

    Look at those three retention figures again: 15 → 13 → 12.

    The interesting thing isn't the absolute level. It's that the curve flattens. Almost everything you lose, you lose on day one. From week one to month one, the line is nearly horizontal.

    That's a meaningfully different diagnosis than "people churn." If the curve kept sliding — 15, then 8, then 3 — the story would be that nothing holds, the product slowly fails everyone, and there's no core. A flat tail says the opposite: there is a group who genuinely stay, and the losses are concentrated in a single place.

    For a solo founder staring at a small user base, this distinction is the difference between despair and a work item. "Nobody sticks" is a reason to quit. "Everybody who survives the first day sticks, and most people don't survive the first day" is a bug report.

    So Where's the Leak?

    The first 24 hours, and specifically the first session.

    Of activated users, roughly four in five are one-and-done: a single session, well under an hour, a handful of messages, never seen again. The people who stayed look completely different — many active days, hundreds of messages, conversations spread across weeks.

    There isn't a gentle slope between those two groups. There's a canyon. Almost nobody has a medium relationship with this product. You either bounce in twenty minutes or you're still here a month later.

    The Signal I Almost Misread

    Here's the pattern that jumped out, and the trap inside it.

    Users who stuck had touched far more characters than one-and-done users — roughly three and a half on average versus a bit over one. The correlation is not subtle.

    The tempting conclusion: exposure drives retention. Get new users to sample more characters and D1 goes up. I nearly built the roadmap on that.

    It's almost certainly backwards.

    The likelier story is reverse causation: people who are already engaged go exploring. Exploration is what engagement looks like, not what produces it. Herding a disengaged user through four character profiles will not make them engaged — it'll make them close the tab from a different page.

    Which means the real lever isn't "more characters." It's the thing the correlation is a shadow of: helping someone find the right character faster, and making the first conversation good enough that they want a second one. Those are different interventions, and only one of them is honest about what the data actually says.

    There's a positioning trap here too. The obvious way to "increase exposure" is to build a browsable feed of characters — which is precisely the product I'm trying not to build. Tendera exists to let you meet one specific written person, not to hand you a carousel. A change that lifts a metric while dissolving the thing that makes the product itself would be a bad trade, and metrics are very good at talking you into bad trades.

    What I Actually Changed

    Three things, all aimed at the first session rather than the funnel above it.

    A matchmaker instead of a default. There was already a personality quiz that routes you to whichever of the four characters actually fits you. It was buried below the fold, under the character grid — so new visitors scrolled past it, or never got there, and defaulted into whoever was featured. I moved the entry point up into the hero, above the character chooser, and cut a redundant intro screen so the quiz opens directly on the first question. Fewer clicks between "I don't know who to talk to" and "oh, her."

    Carrying the conversation through signup. This one still bothers me. Guests can chat five messages before hitting a wall. When they signed up at that wall, the app used to send them to a hardcoded character with an empty chat — throwing away both the person they'd just been talking to and the conversation they were in the middle of. At exactly the moment their momentum was highest. Now signup returns you to the character you were talking to, with the preview conversation migrated into your account, and you keep going mid-thread.

    Tighter re-engagement email. The recall email that goes out after someone goes quiet was fine but flabby. Sharper subject lines, two lines of body instead of four, one clear invitation.

    What I Still Don't Know

    Whether any of it works.

    That's not modesty, it's arithmetic. At a few signups a day, a statistically valid A/B test on retention would take months — and the product will have changed three times underneath the experiment before it concludes. Small products don't get to run experiments; they get to reason from mechanism, ship the most defensible version, and watch the line directionally over weeks.

    So I've picked the interventions I can defend on their logic — remove friction at the moment of highest intent, don't discard someone's momentum, help them meet the right person faster — and I'll watch. If D1 moves, I won't be able to prove which change did it. I've made peace with that.

    The Part Worth Stealing

    Two things I'd hand to any other builder looking at their own numbers.

    Check that your dashboard isn't lying. Any query whose result you treat as "the whole table" is a candidate. Row caps, silent truncation, a `.limit()` you assumed was doing something — these fail quietly and make you confidently wrong. I made product decisions off bad data for weeks and didn't know.

    Read the shape before you read the level. A flattening curve and a sliding curve call for completely different work, even at identical D30. Flat means you have a core and one leak. Sliding means you have a hole in the boat. I spent a while thinking I had the second problem. I had the first one — and the first one you can actually fix.

    ---

    If you want to see the thing all this is in service of, Tendera is four written characters you can talk to without signing up — five messages, no email, no card. The whole bet is that a specific written person beats a configurable one, and the retention data is how I find out whether that's true.

    Ready to meet your AI companion?

    Four unique personalities. Each one remembers you. Free to start.

    Meet Your Match

    Frequently asked questions

    What does retention look like for an AI companion app?

    For Tendera, measured across all history: roughly 71% of registered users send at least one message (activation), and of those, about 15% come back a day later, 13% a week later, and 12% a month later. The shape matters more than the numbers — the curve drops hard on day one and then flattens. Once someone makes it past the first day, they mostly stay.

    Why does a retention curve flattening matter?

    A curve that keeps falling means you're losing people continuously and there's no stable core. A curve that flattens means there is a group who genuinely stick, and your losses are concentrated in one place — for us, the first 24 hours. That changes what you work on: the fight is the first session, not month three.

    Does exploring more characters cause better retention?

    That's the trap. Users who stuck had touched far more characters than one-and-done users. But it's almost certainly reverse-causal — engaged people explore, exploration doesn't manufacture engagement. Forcing a disengaged user to click through more characters won't hook them. The real lever underneath is helping someone find the right character faster, which is a different intervention.

    How do you fix a day-one retention cliff?

    You attack the first session, not the funnel above it. Three things we changed: surfacing a matchmaker quiz before someone defaults into whichever character is featured; carrying the guest's preview conversation through signup so they continue mid-thread instead of landing in an empty chat; and tightening the re-engagement email that goes to people who go quiet. None of these are proven at our scale — we can't run a statistically valid A/B test on the traffic we have.

    Why can't small apps A/B test retention changes?

    Statistical significance needs volume. At a few signups a day, a retention A/B test would take months to reach a conclusion, by which point the product has changed underneath the experiment. Small products have to reason from mechanism — does this change plausibly attack the leak we identified? — ship the most defensible version, and watch directionally over weeks rather than waiting for a p-value.