On this page
- What does a platform actually do for its cut?
- Is it worth it? The honest arithmetic
- What do you need to replace it?
- How do you take the payment?
- How do you stop people downloading the file without paying?
- How do you deliver the file?
- What about VAT?
- What about the 14-day right to cancel?
- What I would do differently
You need three things: a checkout that takes payment, a file store the public cannot reach, and one email. No platform, no percentage. What catches people out is not the code — it is VAT and the EU right to cancel, and both have to be handled before the first sale rather than after it.
This is how the shop on this site works. I built it in a week, it has no database, and everything below is the part I would have wanted written down first.
What does a platform actually do for its cut?
More than people give it credit for, which is why "just use Stripe" is incomplete advice.
A platform gives you a checkout, file hosting, delivery email, a customer receiving their files when your code is broken at 2am, and — this is the big one — it may act as the seller of record, which means the VAT paperwork is theirs and not yours. Check whether yours does. If it does, that is a real service you are giving up.
What it does not give you is the customer. The email address, the ability to say "I revised the book you bought", the ability to offer an upgrade at the right moment. Those are the things worth owning.
Is it worth it? The honest arithmetic
Take a 10% platform cut as the worked example — check your platform's current rate, because these change more often than anyone admits.
| Product price | Platform cut | Sales to cover one build evening |
|---|---|---|
| €9 | €0.90 | ~220 |
| €20 | €2.00 | ~100 |
| €50 | €5.00 | ~40 |
Roughly a hundred sales of a €20 product, and you have paid for the evening you spent building. Below that number the platform is cheaper than your time and you should stay. Above it, the cut never stops and the build was one-off.
The arithmetic flips harder than that table suggests, though, because of one thing: the second sale to the same person. A platform makes that awkward. Your own checkout makes it a link in an email.
The whole list
68 tools, what each costs and when you actually need it. Updated twice a year.
What do you need to replace it?
| Job | What I use | Cost |
|---|---|---|
| Taking payment | Stripe Checkout | per transaction |
| Hosting the site | Vercel | free while building |
| Storing the files | the same host, in a blocked folder | free |
| Sending the email | Resend | free tier |
| Domain | Porkbun | ~€10–15/year |
No database. That surprises people, but Stripe already stores every order, who bought it and what they paid. Adding a database means keeping two records in step and being wrong twice. Instead, the purchase itself is the record: the session id is the key to everything else.
How do you take the payment?
Stripe Checkout, created by your own endpoint. One rule matters more than the rest:
The price is decided on your server, never sent by the browser. The page sends which product. It does not send what that product costs. If your checkout reads a price out of the request, someone will send you a different one, and they will be right to — you offered.
Two settings worth knowing for the EU:
- Tax-inclusive pricing. European consumer prices include VAT by law. Set the line item to inclusive and €20 stays €20 at the checkout instead of becoming €24.20.
- Automatic tax. Stripe works out the rate from the buyer's location, once you have told it where you are registered.
How do you stop people downloading the file without paying?
This is the part I got wrong, and it is worth the whole article.
The files sat in a folder on the same host, with a routing rule pointing that folder at a "forbidden" page. It looked right. It was not: **on the platform I use, that kind of rewrite rule runs after the web server has looked for a matching file.** The rule never fired, because the file was found first. Every PDF was publicly downloadable, and nothing in the code looked wrong.
The fix was to use a redirect rule instead, which runs before the filesystem is consulted. Two lines of configuration, and the difference between a shop and a free library.
The lesson generalises past my host: test it, do not reason about it. Open a private window, paste the direct path to one of your files, and see what happens. Then do it again with ../ in the path. If a customer can guess the URL of a file they have not paid for, your whole product is one screenshot away from a forum.
The delivery link itself should not be a file path at all. It should be an endpoint that takes the purchase reference, asks Stripe whether that purchase is actually paid, checks that this purchase included this file, and only then streams it.
How do you deliver the file?
Two paths, and you want both:
- On screen, immediately. After payment, Stripe sends the buyer to your thank-you page with the session reference. That page asks your own endpoint what this purchase is entitled to and shows the links. This is the path that always works, even when email fails.
- By email, from a webhook Stripe calls when the payment completes.
Build the on-screen path first. Email is the one that fails — a typo in the address, a spam filter, a sending domain that was verified last week and quietly stopped.
One trap I hit: the email library did not throw when the send was rejected. It returned an error object and the code carried on happily, so a failed delivery looked exactly like a successful one. Whatever service you use, check what it returns on failure rather than assuming an exception. Then make a failure tell you about it, because a customer who paid and got nothing will not always write.
What about VAT?
If you sell to consumers in the EU, three things are true and all three are easy to miss:
- Your prices include VAT. A €20 sale at 21% is €16.53 of revenue. Budget with the second number.
- There is a cross-border threshold. Below roughly €10,000 a year in digital sales to consumers in other EU countries, you may charge your own country's rate. Above it, you charge the buyer's country rate and file through the One Stop Shop scheme. Stripe can calculate that; registering and filing is still your job.
- Leaving a platform may move this to you. See the seller-of-record point above.
I am not an accountant, and neither is your checkout. Get the setup confirmed by someone who is before the first real sale, not during your first return.
What about the 14-day right to cancel?
EU consumers have fourteen days to change their mind. For a digital file delivered immediately, that right can be waived — but only if the buyer actively agrees to immediate delivery and acknowledges that agreeing ends the right. A sentence buried in your terms does not count.
The practical version: a required field at the checkout that the buyer must choose, saying in plain words that they want the files now and are giving up the fourteen days. Stripe Checkout supports exactly this as a required custom field, and it takes about ten lines.
Do it properly and your refund policy can be short and honest. Skip it, and a chargeback is a conversation you will lose.
What I would do differently
Build the delivery path before the payment path. It is tempting to start with the checkout, because that is the exciting part and there is a green button at the end of it. But the checkout is the part Stripe has already solved. Delivery — who is allowed to have which file, and what happens when the email does not arrive — is the part that is yours, and it is where every real failure lives.
The Digital Products Playbook
Templates, guides and systems that sell. €20, PDF, instant download.
Tools in this piece
| Tool | What it does | Price |
|---|---|---|
| Stripe | Payments and subscriptions. The standard, and integrating it is a matter of asking. | Free, percentage per sale |
| Vercel | Hosting. Puts a project live on a real address with one command. | Free tier |
| Resend | Transactional email that actually arrives. | Free tier |
| Porkbun | A domain registrar without upsell tricks, with honest renewal prices. | ~€10–15/year |
| Cloudflare | DNS, domains and basic protection. | Free |
| Supabase | Database, accounts and file storage in one, with no backend work. | Free tier |
| GitHub | Version control. Your restore point when an hour of building goes wrong. | Free |
Every tool has its own page with the price, who should skip it and what to check before paying. Some links are affiliate links.
Questions people ask
Can you sell digital products without Gumroad or a similar platform?
Yes. You need three things: a checkout that takes payment, a file store the public cannot reach, and an email that sends the download link. Stripe Checkout handles the first, static hosting handles the second, and a transactional email service handles the third. None of it needs a database.
Is it cheaper to run your own checkout?
It depends on volume. A platform taking a 10% cut costs €2 on a €20 sale, so it earns back the evening you spent building at roughly a hundred sales. Below that, the platform is cheaper than your time. Above it, the cut never stops and the build was one-off.
Who handles VAT if you sell digital products yourself?
You do. Some platforms act as the seller of record and take that job on, which is a real service you give up when you leave. Selling to EU consumers means VAT-inclusive prices, and once cross-border sales pass the €10,000 annual threshold you charge the buyer's country rate and file through OSS.
Do customers get a 14-day right to cancel on digital files?
In the EU, yes, unless the buyer expressly agrees to immediate delivery and acknowledges that this ends the withdrawal right. That consent has to be an active choice at the checkout, not a line in your terms. Stripe Checkout supports it as a required custom field.
How do you stop people downloading the file without paying?
Keep the file out of anything the public web server will serve, and hand it out only through an endpoint that checks the purchase first. Test this by requesting the file path directly. A blocking rule that runs after the file has already been found is not protection.