Launch day is when most tiny SaaS founders make their first public discount code. You open Stripe, make a coupon, type LAUNCH50, paste it into the launch post and the first comment, and go back to replying to people.
That works. It also leaves a code on the internet that you haven't really decided anything about. How long does the 50% last? Who can use it? When does it stop? What happens if it ends up on a coupon site next month?
When you have under 100 customers, every one of those answers shows up in your MRR for a long time. So here's the checklist I'd run before posting the code. It's based on Stripe's coupon and promotion code docs, not on any special trick. Not legal or tax advice.
1. Coupon vs promotion code: post the code, not the coupon
Stripe has two objects and it's easy to mix them up.
- A coupon is the discount logic: percent or amount off, how long it lasts, which products. You (your backend, or you in the Dashboard) apply it to a subscription. The customer never types anything.
- A promotion code is the customer-facing string, like
LAUNCH50, that points at a coupon. Many codes can point at the same coupon.
For a public launch you want a promotion code. That's the one with the controls that matter here: first-time customers only, a minimum spend, an expiry date, a redemption cap, and you can switch it off without deleting anything.
The part people skip: since many codes can point at one coupon, make one code per place you post it. Same 30% off coupon underneath, but PH30 for one launch platform, IH30 for another, NEWS30 for your own email list. Each promotion code has its own redemption count, so a week later you can see which audience actually paid, not just which one clicked. With 20 launch-week signups that's often the only clean attribution you'll get.
2. Pick the duration on purpose
The coupon's duration is the setting with the longest tail. There are three values:
| Duration | What it does | Use it when |
|---|---|---|
once (default) |
Applies to the first invoice only | You want a launch "first month cheaper" |
repeating + duration_in_months
|
Applies to every invoice in that window | You want "3 months at 30% off" |
forever |
Every invoice, indefinitely | Almost never for a public launch code |
Two traps worth knowing:
Forever plus an expiry date doesn't mean what it sounds like. The expiry only stops new people redeeming it. Anyone who used it before the date keeps the discount forever, per Stripe's docs. So a "launch week only, 50% off forever" code is really "everyone from launch week pays half until they cancel". At 15 customers that might be fine. At 60 it's a permanent hole in your revenue that you set up in 30 seconds. If that's actually what you want, it's basically a lifetime deal and deserves the same thinking (I like this breakdown of what a lifetime deal costs a small SaaS later).
Repeating coupons and annual plans. Stripe applies a repeating coupon to every invoice in the window. On a yearly plan, if the subscription renews within that window, the discount covers the whole year. So "3 months at 50% off" on an annual plan can turn into a year at 50% off. If you offer both monthly and yearly, either restrict the coupon to the monthly product or make a separate one for yearly.
3. Set the four limits before you post
On the promotion code (not just the coupon), set these:
-
Expiry date (
expires_at). Pick a real end, like the end of launch week in your time zone. If the coupon itself has aredeem_by, the code can't expire later than that. -
Max redemptions. A cap you'd be happy with even if every redemption came from strangers. The coupon's cap is shared across all codes pointing at it, and a code's cap can't be bigger than the coupon's. So if you make
PH30andIH30from one coupon capped at 50, that's 50 total, not 50 each. -
First-time order only (
first_time_transaction). Stops your existing customers from switching to a cheaper code. But read the next section before you tick it. - Minimum amount (optional). Only checked on the first payment of a subscription, so it's not a way to protect later invoices.
Once a promotion code hits its cap or its expiry date, it's permanently inactive and can't be switched back on. That's what you want. If you need more, make a new code.
4. The first-time-only trap if you ran a trial
This one is easy to miss for launch day specifically.
Stripe's first-time-only rule blocks anyone with prior transaction history. That includes someone who started a PaymentIntent that never completed, and someone who started a trial, even if it was canceled.
Now picture a normal pre-launch: you've had a free trial open for a month, 30 people tried it, and on launch day you email them "here's 30% off if you upgrade this week". If the code is first-time only, a lot of those people will hit an error at checkout. They're exactly the warm people most likely to pay.
So decide which audience each code is for:
- Code for strangers on launch platforms: first-time only is fine.
- Code for your trial users or waitlist: leave first-time off, and use a lower cap or a customer-specific code instead. Promotion codes can be restricted to one specific customer, which is the cleanest option when the list is small enough to do by hand.
5. Make the code actually usable, then check where it shows up
A few things that cause launch-day support emails:
-
Checkout needs the box turned on. If you use Stripe Checkout, the promo field only appears when the Checkout Session is created with
allow_promotion_codesenabled. Easy to forget when you've never used a discount before. Test it with the actual code in test mode. -
Codes are case-insensitive and must be unique among active codes, so
launch50andLAUNCH50are the same thing. You can't have two active public codes with the same string. - The Customer Portal can show and accept codes. If you turned on promotion codes in the portal, customers can apply them when they change plans, and the portal displays the codes already applied to their subscription. If you'd rather a code not get passed around, set limits on it or apply a coupon directly instead. I wrote up the portal settings I'd start with in turning on Stripe Customer Portal before 50 customers.
-
A
oncecoupon disappears from the subscription after it's used. After the invoice that used it finalizes, the subscription'sdiscountsno longer includes it. The invoice still shows it. So if your app reads "does this subscription have a discount?" to show a badge or decide something, it'll say no after month one. Read the invoice if you need history.
When launch week is over
Don't delete the coupon to "end" the promo. Deleting a coupon doesn't remove the discount from subscriptions that already have it; it just stops new redemptions (and archives its codes). Usually the expiry date has already done the job. If a code leaks somewhere you didn't post it, archive just that promotion code by setting it to inactive. Your other codes keep working.
Then look at the per-code redemption counts. That's the useful launch-day data: which audience actually paid with a discount. Whether they'd have paid without one is a separate question, and with this few customers you can just email them and ask. If you're still in the reply-to-everyone phase of the launch, this playbook on turning launch-day comments into paying users pairs well with the per-code numbers.
All of the settings above are in Stripe's coupons and promotion codes docs if you want the API names next to the Dashboard ones.
If you've posted a launch code before: did you make one code for everywhere, or one per platform? And did anyone end up with a "forever" discount you didn't mean to give?
Top comments (1)
the one-code-per-platform thing is underrated. PH30 vs IH30 tells you way more than referrer data ever will with 20 signups