Back to News & Insights
Web Development October 7, 2026 · 6 min read

My Trial Email Promised a Reminder. No Code Ever Sent It.

I build VoiceDash, a white-label client portal for agencies that run voice agents on Retell. Agencies...

My Trial Email Promised a Reminder. No Code Ever Sent It.

I build VoiceDash, a white-label client portal for agencies that run voice agents on Retell. Agencies pay a monthly subscription through Stripe, and first-time customers get a 7-day free trial.

For a while, that trial had a quiet flaw. Checkout never asked first-time customers for a card. So when the seven days were up, Stripe had nothing to charge. The subscription did not convert, it lapsed, and the account lost access without anyone deciding that it should.

The fix was eight lines added and ten removed. The part worth writing about is how I got there, because no single line was wrong. Three pieces of the product each described the trial differently, and nothing forced them to agree.

Here is the shape of the original Checkout session params, trimmed to the part that matters:

paymentmethodcollection: "ifrequired" reads like a sensible default. Do not ask for a card unless you need one. And on day one you do not need one: the first invoice of a trial is $0, so Stripe happily skips the card form. That is exactly what the comment says, and exactly what happened.

The problem is that "required" is evaluated at checkout, not at the end of the trial. Nothing about that setting means "ask for a card later". It means "we did not need one today, so we never got one".

Stripe does let you decide what happens when a trial ends with no payment method on file. There is a trialsettings.endbehavior.missingpayment_method option on the subscription, with choices like canceling or pausing it. I had not set it, and more importantly, I had not designed for the case at all. In my head, a trial was a countdown that ended in a charge. In the code, it was a countdown that ended in nothing.

When a trial started, VoiceDash sent a confirmation email. It said, in bold, "No charge today", and then explained that near the end of the trial, we would ask you to add a payment method to continue.

When I went looking for the code that did that asking, there was none. No handler for Stripe's trial-ending event, no reminder email, no banner nudging people to add a card. The email was describing a flow I had planned at some point and never built. The subscribe page had a softer version of the same promise.

So there were three sources of truth for one business rule: The Checkout params, which said: no card for first-timers. The trial email, which said: no card now, we will ask for one later. The subscribe page, which said: free trial, no charge today, cancel anytime.

Each one was internally reasonable. Read together, they described a product that did not exist: a no-card trial with a reminder flow. The code only implemented the first half.

I think this is a very normal way for billing bugs to happen in small SaaS products. Copy gets written when the plan is fresh. Code gets written when you are trying to ship. Nobody re-reads the email template when they touch the checkout route, because the email template lives in email-templates.ts and feels like marketing, not logic.

It is logic, though. A sentence like "we'll ask you to add a payment method" is a spec. If nothing implements it, it is a broken spec that customers are reading.

Once I saw the gap, the real decision was not "fix the parameter". It was choosing between two different products.

A no-card trial is lower friction at signup. More people start it. But it only works if you build the second half properly: A reminder before the trial ends, with a clear way to add a card. An explicit end behavior in Stripe for the no-card case. An in-app state for "your trial ended, add a card to keep going" that does not look like a broken account. Webhook handling for whatever status the subscription lands in, so your database and your access checks agree with Stripe.

That is a real feature, with real edge cases, and every one of them is a place to drop a paying customer.

A card-up-front trial has more friction at signup, but the end of the trial is boring. Stripe charges the card and the subscription becomes active. The webhook handlers I already had for created, updated, paid and failed invoices cover it. If the card fails, the existing payment-failed path kicks in, marks the subscription past due and emails a link to update the card.

For a B2B tool sold to agencies, who are going to put a card down anyway if they plan to keep using it, the second model was the honest one. It is also the one that matched what I had actually built.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation