Freelance deals often die in the gaps between tools. You send a document for the proposal, a PDF for the contract, an electronic signature link, and then a payment link in another email. I built Pactiamo to collapse all of that into one single link. Here are five technical and product decisions I made along the way and why I stand by them.
Zero client accounts at any cost. Every signup screen a client sees is a place where a deal can fall through. The public share page is entirely server rendered so it works perfectly on slow connections without heavy JavaScript hydration. It captures signatures without requiring authentication. We just record the signer identity directly with the signature including their name, email, and a timestamp. That single constraint drove my architecture more than any specific framework choice.
I take zero percent of payments because I never touch the money. Users simply embed their own Stripe, PayPal, or bank links. I originally considered acting as a merchant of record. But that brings in chargebacks, payouts, and KYC regulations. It is basically a completely different business model. Sticking to a flat subscription keeps the codebase and the trust model simple since the funds never pass through my servers.
I use Postgres for absolutely everything including background jobs and realtime features. I am using the pg boss library for scheduled follow up reminders. It runs right inside the Next.js server using instrumentation.ts. For live notifications like when a client views or signs a document, I use an in memory bus combined with SSE. There is no Redis and no separate queue service. It is just one database to operate. At my current scale boring technology always wins.
Internationalization from day one with English as the strict fallback. The interface supports 10 languages with one rule. Any missing translation key automatically falls back to English. You will never see a raw key string in the UI. Retrofitting internationalization later is the most expensive refactor I have ever experienced. Building that fallback discipline from the start made adding the tenth language just as easy as adding the second.
The AI uses a strict protocol instead of a standard chat box. The drafting assistant communicates using a three mode protocol consisting of chat, patch, and full document modes. This means the model output maps deterministically onto specific editor blocks. A patch is rendered as a reviewable diff for each block with simple apply or reject buttons. It is never just a massive wall of regenerated text. If you cannot diff an LLM output, you cannot trust it in a legal document.
The stack is Next.js App Router, PostgreSQL with Drizzle, the pg boss package, and SSE. It is entirely a solo build.
What would you have done differently? I am particularly curious about the second point. Has anyone here gone the merchant of record route and either loved or regretted it?
Top comments (3)
On decision 1: if a client disputes a signature months later and says that wasn't me, what does the freelancer have to stand on, just the recorded name, email and timestamp, or do you capture more around the signing event? No-account signing feels right for closing deals, that dispute case is just the scenario I kept running while reading.
Good question, and honestly it's the scenario I spent the most time on, because "that wasn't me" is the thing that actually breaks a freelancer if the trail is thin.
It's more than just name, email and timestamp. When someone signs, I also capture their IP address and user agent on the server side from the request headers, not from anything the client sends, and I store all of that in an append only signatures table. On top of that I take a SHA-256 hash of the full document content at the moment of signing. That hash is really the answer to the dispute case. If anyone edits the document afterward, the stored hash no longer matches, so you can prove the exact thing they signed hasn't changed a word. And if the document does get modified later, the existing signatures get voided with a reason and a timestamp instead of quietly carrying over.
There's also a separate view log. Every time the share link gets opened I record the IP, user agent, referrer, timestamp, and how long they stayed and how far they scrolled. So there's a record showing the same person opened the document and actually read it before they signed, not just one anonymous click. On top of that, the client can't sign until the owner has signed first, and once both parties sign the document locks.
The honest limit is that this is evidence, not identity verification. I don't run government ID or KYC checks on the signer, which is the trade-off I accepted for no account signing, so it won't carry the same weight as a notarized or qualified signature. But for the normal "you owe me for the work I delivered" kind of dispute, a matching IP and device, a timestamped read log, and a tamper evident hash of what was actually on the page is a pretty solid record to stand on. If someone genuinely needs regulated grade identity proof, that's a different product than what I'm building.
Thanks for pushing on this one. It's the question that made me start treating signatures as an evidence problem instead of just a UI feature.
That's a real evidence chain. Content hash plus voiding signatures on any later edit wins the ordinary you-owe-me-for-delivered-work dispute, and drawing the line at evidence, not identity, is the honest way to scope it. Signatures as an evidence problem would make a good post title on its own.