<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: migu kk</title>
    <description>The latest articles on DEV Community by migu kk (@migu_kk).</description>
    <link>https://dev.to/migu_kk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2564285%2F30fab227-a43d-4f3c-b4c6-3d9f19428ff2.png</url>
      <title>DEV Community: migu kk</title>
      <link>https://dev.to/migu_kk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/migu_kk"/>
    <language>en</language>
    <item>
      <title>E-signing without accounts: designing signatures that are anonymous but still hold up</title>
      <dc:creator>migu kk</dc:creator>
      <pubDate>Wed, 15 Jul 2026 14:43:39 +0000</pubDate>
      <link>https://dev.to/migu_kk/e-signing-without-accounts-designing-signatures-that-are-anonymous-but-still-hold-up-515c</link>
      <guid>https://dev.to/migu_kk/e-signing-without-accounts-designing-signatures-that-are-anonymous-but-still-hold-up-515c</guid>
      <description>&lt;p&gt;&lt;a href="https://pactiamo.com" rel="noopener noreferrer"&gt;Pactiamo&lt;/a&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a signature on an ordinary contract actually needs
&lt;/h2&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Intent. The person meant to sign, and the interface made that obvious.&lt;/li&gt;
&lt;li&gt;Attribution. You can tie the signature to a specific person.&lt;/li&gt;
&lt;li&gt;Record integrity. The signed version is preserved and can be produced later, unchanged.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The share link is the capability
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Raising attribution with an email round trip
&lt;/h2&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The signer enters their email.&lt;/li&gt;
&lt;li&gt;A Cloudflare Turnstile challenge runs first, so the send-code endpoint isn't a free email cannon for anyone to abuse.&lt;/li&gt;
&lt;li&gt;The server emails a six digit code, and the client posts it back.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap: the owner opening their own link
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I stopped, stated plainly
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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: &lt;a href="https://www.producthunt.com/products/pactiamo?launch=pactiamo" rel="noopener noreferrer"&gt;https://www.producthunt.com/products/pactiamo?launch=pactiamo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Building Pactiamo, proposals, contracts, and e-signing in one link.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>security</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Building Pactiamo solo and 5 decisions I am glad I made</title>
      <dc:creator>migu kk</dc:creator>
      <pubDate>Mon, 13 Jul 2026 13:49:16 +0000</pubDate>
      <link>https://dev.to/migu_kk/building-pactiamo-solo-and-5-decisions-i-am-glad-i-made-10c3</link>
      <guid>https://dev.to/migu_kk/building-pactiamo-solo-and-5-decisions-i-am-glad-i-made-10c3</guid>
      <description>&lt;p&gt;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 &lt;a href="https://www.pactiamo.com" rel="noopener noreferrer"&gt;Pactiamo&lt;/a&gt; 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.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The stack is Next.js App Router, PostgreSQL with Drizzle, the pg boss package, and SSE. It is entirely a solo build.&lt;/p&gt;

&lt;p&gt;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?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>saas</category>
      <category>proposal</category>
    </item>
  </channel>
</rss>
