different species of crabsoft-shell crabvietnamese mud crab
7
12 Comments

The design I offered in public did not survive contact

I'm Yarin. I'm the solo founder of RecoverStack, a dunning tool, which means I have a vested interest in making failed payments sound like a bigger problem than they are. It's worth saying that up front, because the following post is mostly about a part of that problem my product doesn't touch at all.

Two weeks ago, Shahrzad Ramezanpour, a product owner at Actionist, posted a long thread about a failed Stripe add-on renewal. Her product sells a base plan + paid add-ons that grant extra capacity: agents/storage/trigger runs/compute/etc. When an add-on renewal fails, Stripe reports the failure and then tells you nothing about what to do next, which is the actual hard part.

Internally, her product only sees one combined allowance. A customer sitting on 20 agents has no marker for which 10 came from the base plan. Storage has no per-file provenance. They'd settled on a 7 day grace window and she said herself that this move raised more questions than it answered.

My take on it was offering documenting resource origin when it's created, tagging each agent and storage block and trigger allowance with the subscription item that paid for it, so revocation turns into a filter instead of a guess. I've also offered they should tie the grace window to the retry schedule instead of a round number, because a 7 day grace with a retry pending on day 8 restricts someone whose payment is still in-flight.

She replied in 11 minutes and took the my take apart.

Capacity is fungible - A customer can delete agents to free base capacity, build new resources on that capacity, and then buy an add-on months later & revert. Which is base and which is add-on at that point? It's worse for shared resources too, where a single agent execution can consume base and add-on compute at the same time, so it isn't clear what's even being tagged. Her close: "Doesn't that eventually turn provenance into an accounting problem rather than a reliable source of truth?"

She was right. Provenance-per-resource only holds while a unit stays attached to the thing that bought it, and her case broke exactly that.

The second answer, after conceding on it for a bit, was to stop tagging resources and track the ceiling instead (Base allowance + active add-ons). When one lapses, the only question now is whether they're over the new total or not, then split by resource type. Stock like agents and storage persists, so someone has to make a reclaim decision. Flow like trigger runs and compute is already consumed, so there's nothing to reclaim and you only re-limit going forward. And finally, let the customer choose what to drop rather than deriving it.

She liked the stock vs flow split, and then she killed the second half of that. Asking the customer to reclaim doesn't work, because the resources sitting over the limit are usually dependencies for live workflows, and they can't see what's connected to what, especially for the invisible ones like storage/trigger fire-up/VPS. Any choice they make is a blind guess that can take down their prod.

So what we settled on was to not delete anything, and not ask them to either, but to freeze instead. Over-ceiling stock goes read-only or throttled rather than removed, so a running workflow keeps working, it just can't really grow until the card is fixed. Flow caps from the next period on its own, so there's nothing to reclaim and no destructive choice ever lands on the customer.

I got to this solution because the person behind a failed add-on charge probably still wants to pay you. A restriction that can break their production punishes the exact customer you're trying to recover, but freezing keeps the pressure reversible.

I don't think my first answer was a bad instinct so much as a lack of information problem. I didn't have her fungibility case and she did, because she lives the problem. A conversation with someone technical who has the problem for real clears that up fast, and then both sides end up somewhere neither of them started from.

Then I went and checked my own product, because I've just told a stranger not to tie a grace window to a round number.

RecoverStack's own grace period is GRACE_PERIOD_DAYS = 14, in account-degradation.service.ts. A flat calendar countdown from past_due that doesn't look at the decline-code retry schedule in decline-engine.service.ts at all. The exact shape I'd warned her about an hour earlier is also present in the one place I control end to end.

To be fair to it, that grace period governs RecoverStack's own billing, which is single plan without add-ons, so a flat window is a lot more defensible there than in her multi-item case. I'm not going to call myself a hypocrite for a better story, but I hadn't looked, and I only did look because she pushed back on this.

The initial codebase, like most bootstrapped SaaS, got built in haste, and parts not communicating with each other is pretty natural in of itself. What isn't automatic is noticing these things. I work as a manager over coding agents, so I usually don'd deep dive into own implementation details the way you do when you type them out.

RecoverStack has no position on this seam yet. There's no outbound signal a merchant's product could subscribe to, no per-item granularity anywhere in the schema, and there's a service that deliberately sums across subscription items into one lump so multi-item subs aren't understated in a warning email. So it has already met this exact case once, and chose to aggregate away the detail she needs.

Is that a boundary or a gap? It does sound like something that could fit naturally, and recovery stopping at the card while still telling you exactly what failed and when is a clean line to draw. But I have 0 customers and 0 recovery data, so I'd be taking guesses, and real feedback will lock it in.

That's the actual thing I'm taking with me. Direct field experience from someone with a real, tangible pain is worth a thousand times more than throwing things at a wall to see what sticks. I need to figure out how to have more conversations like that one.

If you've drawn this line in your own product, I'd like to know where you put it and what broke. Contact me at [email protected], or just reply here.

posted to Icon for group Building in Public
Building in Public
on August 3, 2026
  1. 1

    the constraint wasn't missing provenance data, it was that nothing stays attached to whatever paid for it. the tell is in her shared-compute example: one agent execution burning base and add-on compute at the same instant. once a unit can be spent jointly, any tag you write is a bookkeeping convention you have to defend forever, not a fact you can read back.

    and the second design died of the same thing one level up. asking the customer to drop something assumes slack exists, but over-ceiling resources are load-bearing.

    so i'd stop treating it as a reclaim problem. freeze creation, let the over-limit delta sit as a marked state until retries actually exhaust, and never delete on their behalf. probably annoying to explain in the ui.

    (i sell a dunning tool, so grain of salt.)

  2. 2

    "This is one of the most honest posts I've read here. The part about checking your own code after giving advice — and finding the exact pattern you warned against — is something I think a lot of founders have done but few admit to.

    I'm building Rallynex (AI workspace for founders building in public), and I've had similar moments where I realize I'm building something that solves a problem I think exists, rather than one I've actually validated.

    Two things stood out to me:

    1. The 'boundary vs. gap' question is something I've been wrestling with too. With Rallynex, I keep asking myself: 'Am I building a tool that helps founders document their journey, or am I building a tool that helps founders grow an audience?' They're related, but they're not the same. And I think I've been trying to solve both at once.

    2. 'Direct field experience from someone with a real pain is worth a thousand times more than throwing things at a wall' — this is the hardest lesson I'm learning. I spent weeks building features I thought people wanted, but the moment I started talking to real founders, I realized I was guessing. Now I'm spending more time asking questions and less time building.

    Curious — how do you plan to get more of those real conversations? Are you reaching out to founders directly, or are you relying on posts like this to bring them to you?"

    1. 1

      Both really, and the direct half is going badly enough to be worth saying out loud.

      For cold outreach, I've sent a handful of first-touch DMs to founders whose posts showed a real billing problem. Almost none replied. One did, to tell me the reach was not sound and that he only answered because he remembers starting from zero. That was the most useful reply I've had, and it changed how I write messages from that point.

      What I'm currently trying instead (and is actually working for a bit) is answering a specific question in public and offering to look at their data by hand. Someone posted their trial-conversion failures last week, I offered to go through the decline codes with him, and 8 hours later he'd pulled the full breakdown and posted it publicly. That's one conversation out of maybe thirty comments, but it has some real value.

      So posts like this are the slower half. Usually the comments underneath them are doing more work than the posts themselves.

  3. 2

    “Is that a boundary or a gap?” feels like the real question here.

    What evidence would you need from merchants to decide that post-failure product behavior belongs inside RecoverStack, rather than being an adjacent problem the merchant should continue owning?

    1. 1

      Honestly? I'm not sure yet...

      I think I'd just ask operators "When a renewal fails today, what actually changes in your product?". If most of them respond with "nothing - until someone notices manually", that's a gap, because the decision isn't being made deliberately, it's being skipped and moved around. If most describe a real policy they built on purpose, it's a boundary - and my job is just to hand them better events than Stripe does.

      The second thing I'd want to know is whether they want a signal or a decision made for them (Those are different product positions). A merchant who wants to be told "this subscription item failed, here's the decline code and where it sits in the retry schedule" wants me to stay on my side of the line. A merchant who wants access downgraded for them automatically and systematically is asking for something else entirely.

      Right now I have 0 customers, so anything I decide from here is more of a guess. The one datapoint I do have points at the fact it's a gap: a payments-heavy commenter on r/stripe told me this week that most operators don't catch failed renewals at all, because nothing in the billing system tells them. But that's one person's read, and I'd rather ask ~10 merchants the question above than build on it.

      1. 1

        That’s helpful context. I appreciate you being candid about what you know versus what you’re still testing.

        I’d like to continue the conversation outside the thread. What’s the best email to reach you on?

  4. 1

    The freeze conclusion matches where we landed on a metered API, though the resource shape is different enough that the seam question is the more interesting half.

    On boundary versus gap: what a merchant's product actually needs from the recovery side is not an event stream of which subscription items failed. It is a current entitlement state it can read, with an effective-from timestamp. Our enforcement point is a per-request check, which makes an event handler the wrong home for it: webhooks get missed, replayed and delivered out of order, and any cache built from them drifts silently in the direction of granting access that is no longer owed. A pull-able "what is this account entitled to right now" cannot drift. Emitting events as well is fine, but if events are the only interface, every merchant reimplements the same fragile projection and gets it subtly wrong.

    One thing worth adding next to freeze and delete: degrade the artifact. We keep production and sandbox quotas separate and sandbox output comes back watermarked, so a caller who is out of production quota can still run their whole integration end to end and gets a real file back, it just is not one they can ship to their own customer. Same reversibility you are after, and it fails in the direction of "your pipeline still runs" instead of "your pipeline throws".

    On your own grace window: single plan without add-ons is a fair defence, and the more interesting part is that you only went and looked because someone pushed back on you. That is the habit worth keeping, more than the fix.

    Great idea I think!

  5. 1

    "This is really honest — and I think you're right about comments doing more work than posts. I've noticed the same thing: the real connections happen in the replies, not the original post.

    Your point about cold DMs failing is helpful too. I've been debating whether to try that for Rallynex, but it sounds like it's a low-return move early on. What changed in your message writing after that founder gave you feedback? Curious what you tweaked."

  6. 1

    the fungibility point is such a good catch, and ngl the whole arc here mirrors something I went through with a commenter on a post about FX rate benchmarking. my first instinct was clean and defensible in theory, then someone who actually lives the problem pointed out exactly where it breaks in practice. the freeze-instead-of-delete solution you landed on is smart too, reversible pressure beats punishing the customer you're trying to recover

  7. 1

    Freezing is reversible, but it can quietly turn a failed payment into unbounded cost. I’d keep reads, freeze writes, cap new compute, and emit one clear entitlement event. That lets RecoverStack inform the decision without owning the merchant’s whole product policy.

  8. 1

    The title is the whole lesson: design that hasn't touched real constraints isn't design, it's a sketch.

    The part that stood out is the provenance problem. You can design a beautifully logical degradation system, but if the underlying data never tracked which capacity came from which plan, the logic has nowhere to sit. The design fails not because it was wrong conceptually, but because the data model doesn't support it.

    This is the kind of thing you only find by posting it and having someone who lives inside the actual system tell you what you missed. Building in public isn't just accountability. It's the fastest way to find the edge cases you couldn't see from the outside.

  9. 1

    This is probably one of the most underrated parts of building in public.

    A design can look perfect when you're inside the product every day, but the moment someone else tries to use it, they reveal all the assumptions you didn't notice.

    I've seen this happen a lot with landing pages too. Founders often explain the product the way they built it, but users are trying to understand it from a completely different angle: "Is this for me?" "Why should I care?" "What happens if I try it?"

    The feedback loop between what you imagine and what people actually understand is where the real product starts taking shape.

Trending on Indie Hackers
How to rank #1 on ChatGPT? User Avatar 112 comments I built a startup-idea scanner. It just told me none of my 3,400 ideas are easy wins. User Avatar 75 comments “I’ll just post on Upwork” is not a client strategy. Here’s what I built instead. User Avatar 54 comments Building a Shopify bundles app for stores with real fulfillment: here's the wedge User Avatar 42 comments I recorded myself using 200+ indie SaaS products cold. Here are the 7 conversion killers that keep showing up. User Avatar 33 comments How to automate refund reviews without giving AI the final say User Avatar 29 comments