Pactiamo lets a freelancer send a contract as a link, and the client signs it without making an account. That constraint is the whole product, and it puts two goals in direct tension. The signer should have zero setup. The signature should still be attributable and hard to repudiate later. Here is how I reconciled those two, and where I decided to stop.
What a signature on an ordinary contract actually needs
Before writing any code I had to get honest about the requirement. For everyday commercial agreements, a freelance statement of work, a small studio's service contract, what makes an electronic signature usable is not cryptography. It is three plainer things:
- Intent. The person meant to sign, and the interface made that obvious.
- Attribution. You can tie the signature to a specific person.
- Record integrity. The signed version is preserved and can be produced later, unchanged.
None of that requires a blockchain, a certificate authority, or a signing PKI. Those exist for a different tier of document. Reaching for them here would have bought me operational weight and a worse signer experience in exchange for reassurance I could get more cheaply. So I didn't.
The share link is the capability
There are no client accounts, so I can't put signing behind a login. Instead the share URL carries an unguessable token, and possession of that link is what grants access to the document. The share page renders on the server. The signer opens it, reads, and signs in place.
Signing captures identity at the moment it happens: the name they type and the email they enter, stored next to the signature value and a timestamp. The signature itself is either a drawn image or a typed name, but the part that matters is the metadata pinned to it, not the pixels.
Raising attribution with an email round trip
A typed name proves very little on its own. For contracts where the sender wants more, there is an optional email verification step that runs before the signature commits. It goes like this:
- The signer enters their email.
- A Cloudflare Turnstile challenge runs first, so the send-code endpoint isn't a free email cannon for anyone to abuse.
- The server emails a six digit code, and the client posts it back.
- On success the server hands back a short lived sign token, and only a write request carrying that token is allowed to record the signature.
This is opt in per contract, controlled by a flag on the document, because not every agreement is worth asking the client to go check their inbox. When it is on, the signature stops being "someone typed a name." It becomes "someone who could receive mail at this address, and clear a bot check, signed at this time." That is a real jump in attribution for very little friction.
The trap: the owner opening their own link
Here is the bug that taught me the most, and it isn't cryptographic at all. The document owner naturally wants to open their own share link to see what the client sees. If you treat every visitor to that link the same way, the owner lands on an interactive document and can sign the client's field themselves, which is the exact outcome the product exists to prevent.
The fix is a preview mode. When the logged in visitor is the document owner, the share page renders read only. Every interactive field is locked, and the backend independently refuses a signature on a client field when the request comes from the owner, so the guard does not live only in the UI. Roles are gated both directions: the owner can sign the fields that belong to them, the client can sign theirs, and neither can stand in for the other. A confusing 403 would have been a design bug. Silently letting the owner forge the client's signature would have been a far worse one.
Where I stopped, stated plainly
A threat model you don't write down is one you're lying to yourself about. Here is what this protects against and what it does not.
It protects against later tampering, because the executed document is preserved and emailed to both parties the moment it is signed, so the record does not depend on my server staying up. It protects against casual repudiation, the "I never saw this" claim, especially with the email step turned on.
It does not protect against a determined impersonator who already controls the client's inbox. If someone can read the verification code sent to that address, they can clear the check. That is the same boundary every mainstream e-sign tool lives with. I would rather say it out loud than imply a level of proof the design does not deliver.
The takeaway
Most of the work here was subtraction. Deciding what a signature on an ordinary contract does not need is what let me keep the signer's side down to opening a link, while the parts that actually carry weight, identity captured at signing, an optional verified email, an immutable emailed record, and strict role separation, stay boring, legible, and cheap to run.
Stack, for the curious: Next.js App Router, server rendered share pages, Postgres via Drizzle, Resend for the record and verification email, Cloudflare Turnstile on the code endpoint.
If you've shipped signing in a product, I'd like to hear where you drew the line, especially if you went further up the assurance ladder and felt it paid off.
Pactiamo went live on Product Hunt a few weeks ago, and the launch page is here if you want to see it in the wild: https://www.producthunt.com/products/pactiamo?launch=pactiamo
Building Pactiamo, proposals, contracts, and e-signing in one link.
Top comments (0)