The bill that made no sense
A signature is about 400 bytes of JSON and a hash.
Every big e-signature product will charge you somewhere between $1 and $4 for each document you send. They call it an "envelope". On top of that you pay per seat, so every colleague who needs to send a contract is another line on the invoice. Run out of envelopes on the 22nd of the month and your deal waits until the 1st. I have watched a real contract sit still because a counter hit zero.
So I looked at what an e-signature product actually costs to run, and priced Putmysign around that.
What actually costs money
Storage is the obvious guess, and it is wrong. Uploads are capped at 5 MB. A heavy account sends a few hundred documents a year, which is a few gigabytes. At object storage prices that is cents. Per year.
The cost that actually grows with use is sending a document: an email to every recipient, reminders, and the bandwidth when they open the PDF. Fractions of a cent each time, but it is the one number that scales.
It is also the number customers already think in. Nobody knows how many gigabytes they store. Everyone knows roughly how many contracts they send.
So Putmysign meters exactly one thing, documents sent per month:
- Free: 10 documents a month, up to 10 recipients each, signing links live up to 30 days
- Pro, $19/month: 100 documents a month, up to 50 recipients each, links live up to a year, automatic reminders
- Every plan: files and audit trail kept forever, unlimited signatures, unlimited reusable templates, no seats, recipients never make an account
Drafts don't count. The allowance resets on the 1st of the month (UTC), not on a rolling 30-day window, so you always know when it comes back.
On Pro that works out to 19 cents a document, against $1 to $4 elsewhere.
That one pricing decision shaped most of the technical choices underneath. That is the interesting part.
1. Keep the file, and keep the proof
Every signed PDF is kept. Storage is cheap, so there is no reason to make anyone think about retention.
The file alone is not proof, though. So every document records two hashes:
const originalHash = sha256(originalBytes);
// signatures applied, certificate page added
const finalHash = sha256(finalBytes);
The original is hashed on upload, the final one after the signatures and the audit certificate page are applied. If you download your copy, you can hash it and check it against a record I cannot quietly change. Your proof does not depend on trusting my storage.
2. Recipients never make an account
A signup wall in the middle of someone else's contract is the biggest reason paperwork stalls. It mostly exists so vendors can count seats.
So the signing link is the login. It is an HMAC over the document and the recipient, built from a server-only secret. Only the hash of the token is stored:
const secret = process.env.SIGNING_TOKEN_SECRET ?? process.env.SESSION_SECRET;
if (secret.length < 32) throw new Error("must be at least 32 characters");
return createHash("sha256").update(token).digest("hex");
Store the hash, not the token. If my database leaks tomorrow, nobody gets a working signing link out of it. Same idea as hashing passwords, applied to a URL.
3. Whatever you meter, check what slips past it
If you count documents, a single document with unlimited recipients is an unmetered mailing list. Someone on the free plan sent one document to 664 addresses, every one of them an email from my domain.
So there is a per-document recipient cap: 10 on Free, 50 on Pro. Real approval rounds rarely go past a handful of people, and the cap closes the gap the meter left open.
Any usage-based price has a hole like this. Find it before someone else does.
4. One row per document
A signing session is a burst of tiny writes. Opened. Viewed page 3. Signed field 2. IP recorded. All of it against one document that is only ever read as a whole.
Splitting that across six tables buys me joins I never run. So each document is one JSONB column holding everything, with the fields I actually filter on copied out into real indexed columns: owner, status, updated date. A GIN index on the JSON handles signing-link and "shared with me" lookups.
The real risk with a single row is two people signing at the same moment. Last write wins, one signature disappears. I fixed it the boring way, with SELECT ... FOR UPDATE inside a transaction.
Postgres has been a fine document database for years, and it still hands you row locks when you need them.
5. Thumbnails without a thumbnail service
Page previews come from pdftoppm, part of poppler. It is a 25 year old C program. I shell out to it, render the page when someone asks for it, and cache the image back into the bucket next to the PDF.
No render service, no queue, no third party API with a per page price on it. When your price is per document, anything that adds a per-page cost underneath eats straight into it.
The stack, quickly
React Router 7, TypeScript, Postgres with Prisma, any S3 compatible bucket (MinIO locally, so the whole thing runs on a laptop with one docker compose up), Firebase Auth for owners only, Resend for email with bounce handling, Paddle for billing.
Nothing fancy. The interesting decisions are all about what is missing.
Things worth stealing
You do not have to care about e-signatures to use any of this:
- Charge for what actually costs you money, and check the bill before deciding what that is. It is often not what you'd guess.
- Meter the unit customers already think in. Documents sent, not gigabytes.
- Whatever you meter, look for what slips past it. Count documents and someone will put 664 people on one.
- Keep checkable proof, not just the file. Hashes are tiny and they make your records verifiable without trusting you.
- A signup wall in someone else's workflow is a tax on your own customer. They pay it in deals that stall.
-
JSONB, plus a few copied-out indexed columns, plus
FOR UPDATEcovers a lot of "we need a document database" situations.
It is live at putmysign.com. Free tier, no card. Upload a PDF you already have and send it to someone who will never make an account.
If you can break the signing link logic, I would genuinely like to hear about it in the comments.
What is the most backwards pricing unit you have run into? I will start: per seat pricing on a read only viewer.
Top comments (2)
The "delete the file, keep the proof" idea is brilliant — the hashes are a few bytes but the accountability is permanent. And "two clocks on one object will drift, derive one from the other" is going straight into my notes.
To answer your question: the most backwards pricing I've personally hit is per-email limits on auth. I'm 12 and I built an AI coding mentor app entirely on my Android phone (free tiers only). On launch day I hit the Supabase confirmation-email rate limit — gating the exact email that lets a user INTO the product felt exactly like your envelope story. Charging for the cheap thing, blocking the important thing.
Also huge respect from a fellow Indian builder (I'm in Tamil Nadu). Storing the hash of the HMAC token instead of the token itself is the kind of detail I'm trying to learn more of as I grow.
I'll play with the free tier this weekend and try to break the signing link logic. If I find anything, I'll report back. 🚀
Love the idea—charging for storage instead of every signature makes e-signatures feel much more transparent. 🔥
But if you’re building a SaaS product, don’t let technical SEO issues silently limit your growth. A quick audit can uncover broken links, indexing problems, slow pages, missing metadata, and other ranking blockers.
Check your website with SerpSpur SEO Audit Tool and turn SEO problems into actionable opportunities. 🚀