Technical Deep Dive

Signnee — Build From Scratch

What it actually takes to build a DocuSign clone without DocuSeal. Every component, library, gotcha, and hour estimate.

⚖️ DocuSeal Now vs Build From Scratch Later

Current Plan — Use This
White-label DocuSeal
$0/mo
4–5 days to launch · Self-hosted on existing DO droplet
  • PDF engine already built and battle-tested
  • Signing UI works on all devices
  • Webhooks, REST API, multi-signer — all done
  • Audit trail + legal compliance built in
  • You just build the branded Next.js shell
  • 10,000+ GitHub stars — well maintained
Future Option — If You Want Full Ownership
Build From Scratch
~3–4 weeks
5–7 major components · ~$20–30/mo infra · Full control
  • Own every line — no dependency on DocuSeal
  • Customize anything (UI, flow, branding)
  • No risk of DocuSeal going paid/closed
  • Deeper integration with your other SaaS tools
  • Sell the codebase or white-label it yourself
  • Complex but learnable with the right libs
Bottom line: Use DocuSeal now to ship fast and start making money. After 6 months and 50+ paying clients, evaluate migrating to the scratch-built engine. The revenue funds the rebuild.

🧩 The 7 Components You'd Need to Build

1. PDF Renderer

Hard — 4–5 days

Render each PDF page as a high-resolution canvas image so users can drag and drop signature fields on top of the correct positions.

Library: pdfjs-dist (Mozilla's PDF.js)
⚠️ Hardest part. Fonts, images, embedded objects all render differently.
import * as pdfjsLib from 'pdfjs-dist'

const pdf = await pdfjsLib.getDocument(fileUrl).promise
const page = await pdf.getPage(1)
const viewport = page.getViewport({ scale: 1.5 })
const canvas = document.getElementById('pdf-canvas')
const ctx = canvas.getContext('2d')

await page.render({ canvasContext: ctx, viewport }).promise
// Now overlay field positions on top of this canvas

2. Field Placement UI

Hard — 3–4 days

Drag-and-drop interface to place signature, date, text, initials, and checkbox fields at exact pixel coordinates on the PDF page.

Libraries: react-dnd or @dnd-kit/core + fabric.js
⚠️ Must store x/y as percentages (not pixels) so they work at any zoom level.
// Store field as % of page dimensions
const field = {
  type: 'signature',  // signature | text | date | checkbox
  page: 1,
  x: 0.32,    // 32% from left
  y: 0.74,    // 74% from top
  width: 0.25,
  height: 0.06,
  required: true,
  signer_role: 'Client'
}

3. Signature Capture

Medium — 2 days

Canvas-based signature drawing for mouse and touch. Must capture as SVG or PNG. Also support typed signatures and uploaded image signatures.

Library: signature_pad (Szymon Nowak — 9K stars, used by DocuSign)
✓ This library is well-solved. Easy to implement.
import SignaturePad from 'signature_pad'

const canvas = document.getElementById('sig-pad')
const pad = new SignaturePad(canvas, {
  backgroundColor: 'rgba(255,255,255,0)',
  penColor: '#000000',
})

// Get signature as PNG data URL
const dataURL = pad.toDataURL('image/png')

// Or as SVG for vector quality
const svgString = pad.toSVG()

4. PDF Finalization

Hard — 3–4 days

After all parties sign, embed actual signatures as images into the PDF at the correct coordinates. Flatten the PDF so fields are baked in and cannot be altered.

Library: pdf-lib (MIT license, excellent docs)
⚠️ Font embedding, image scaling, and coordinate math are tricky.
import { PDFDocument } from 'pdf-lib'

const pdfBytes = await fetch(originalPdfUrl).then(r => r.arrayBuffer())
const pdfDoc = await PDFDocument.load(pdfBytes)
const page = pdfDoc.getPage(0)
const { width, height } = page.getSize()

// Embed signature image at field coordinates
const sigImageBytes = Buffer.from(sigDataUrl.split(',')[1], 'base64')
const sigImage = await pdfDoc.embedPng(sigImageBytes)

page.drawImage(sigImage, {
  x: field.x * width,
  y: height - (field.y * height) - (field.height * height),
  width: field.width * width,
  height: field.height * height,
})

const finalPdfBytes = await pdfDoc.save()
// Upload to Supabase Storage

5. Signer Portal (No-Auth)

Medium — 2–3 days

Each signer gets a unique URL with a signed JWT token. No account needed. Token expires after X days. Validates identity via email click.

Libraries: jose (JWT) + nanoid
✓ Manageable once you have the signing UI working.
import { SignJWT } from 'jose'

// Generate signer token (sent in email)
const token = await new SignJWT({
  submission_id: 'sub_abc123',
  signer_email: 'client@example.com',
  role: 'Client',
})
.setProtectedHeader({ alg: 'HS256' })
.setExpirationTime('7d')
.sign(secret)

// Signer URL:
// https://signnee.online/sign/{token}

// Verify on page load
const { payload } = await jwtVerify(token, secret)
// payload.submission_id → load document

6. Audit Trail

Medium — 2 days

Every action (opened, signed, IP address, timestamp, user agent) must be recorded in a tamper-evident log. Required for legal admissibility (ESIGN Act, UETA).

Libraries: crypto (Node built-in) + Supabase
✓ Mostly database design + hash chaining.
// Each audit event hashes the previous event
// creating a tamper-evident chain
const auditEvent = {
  submission_id,
  event: 'signed',      // opened|signed|declined|completed
  signer_email,
  ip_address: req.headers['x-forwarded-for'],
  user_agent: req.headers['user-agent'],
  timestamp: new Date().toISOString(),
  prev_hash: previousEventHash,
}

const hash = crypto
  .createHash('sha256')
  .update(JSON.stringify(auditEvent))
  .digest('hex')

// Store event + hash in audit_log table

7. Email System

Easy — 1 day

Transactional emails for: signing request, signed notification, completion with PDF attached, reminders for pending signers, declined alerts.

Library: resend + react-email for templates
✓ Resend SDK is simple. React Email makes templates easy.
import { Resend } from 'resend'
const resend = new Resend(process.env.RESEND_API_KEY)

// Send signing request
await resend.emails.send({
  from: 'sign@signnee.online',
  to: signerEmail,
  subject: `Please sign: ${documentTitle}`,
  react: SigningRequestEmail({
    signerName,
    documentTitle,
    signingUrl: `https://signnee.online/sign/${token}`,
    expiresIn: '7 days',
  }),
})

// Attach completed PDF
await resend.emails.send({
  from: 'sign@signnee.online',
  to: [senderEmail, signerEmail],
  subject: `Signed: ${documentTitle}`,
  attachments: [{ filename: 'signed.pdf', content: pdfBuffer }],
})

📦 All npm Packages Needed

PackagePurposeWeekly DownloadsDifficulty
pdfjs-dist Render PDF pages as canvas for field placement UI ~4M/wk Hard — complex API
pdf-lib Embed signatures + text into final PDF, flatten document ~800K/wk Medium — good docs
signature_pad Canvas-based signature drawing (mouse + touch) ~200K/wk Easy — drop in
@dnd-kit/core Drag-and-drop for placing fields on PDF canvas ~1.5M/wk Medium
jose Sign/verify JWT tokens for signer portal URLs ~5M/wk Easy
nanoid Generate unique submission and document IDs ~10M/wk Easy — one-liner
resend Transactional emails (signing requests, completions) ~300K/wk Easy
react-email Build HTML email templates in React ~200K/wk Easy
sharp Resize/optimize signature images before embedding ~5M/wk Easy
date-fns Expiry dates, timestamps, audit trail formatting ~8M/wk Easy

🗄️ Database Schema (From Scratch)

TableKey ColumnsPurpose
sn_documents id, user_id, title, original_pdf_url, signed_pdf_url, status, created_at Master document record
sn_fields document_id, type, page, x, y, width, height, required, signer_role Field positions on each PDF page (as % coordinates)
sn_signers document_id, email, name, role, order, token, signed_at, status Each person who needs to sign + their token
sn_field_values field_id, signer_id, value_type, value_text, value_image_url, signed_at What each signer entered/drew for each field
sn_audit_log document_id, signer_id, event, ip_address, user_agent, timestamp, prev_hash, hash Tamper-evident chain of all events
sn_templates user_id, name, original_pdf_url, fields_json, thumbnail_url Reusable document templates with pre-placed fields
sn_webhooks user_id, event_type, target_url, secret, active Per-user n8n/Make.com endpoint configs
sn_subscriptions user_id, stripe_sub_id, plan, docs_used, docs_limit Plan limits + Stripe billing

⚖️ Legal Requirements (ESIGN Act + UETA)

For e-signatures to be legally binding in the US, you must satisfy these requirements. DocuSeal handles all of these. Building from scratch means you implement each one.

🗓️ Build Timeline — From Scratch

Week 1

PDF Engine + Field Placement

The hardest week. Get pdfjs-dist rendering reliably, build the field drag-and-drop system, store field coordinates as percentages.

PDF.js renderingPage navigationField drag/dropCoordinate storageZoom handling
Week 2

Signer Portal + Signature Capture

Build the no-auth signing page. JWT token system. signature_pad integration. Typed + drawn + uploaded signature modes.

JWT token genSigner portal pageSignature padTyped signatureField validation
Week 3

PDF Finalization + Audit Trail

Embed signatures into PDF using pdf-lib. Flatten document. Build tamper-evident audit log with hash chaining. Legal consent flow.

pdf-lib embedPDF flattenAudit logHash chainConsent flowPDF hash storage
Week 4

Email + Webhooks + Dashboard + Stripe

Resend email system, webhook relay to n8n/Make, main dashboard UI, templates system, Stripe subscription billing.

Email templatesWebhook relayDashboard UITemplatesStripe billingMulti-signer order
Total: ~3–4 weeks for a solid MVP. Add 1–2 weeks for edge cases: mobile signature UX, PDF rendering bugs on complex docs, email deliverability, cross-browser canvas differences.

📊 DocuSeal vs Scratch — Feature by Feature

FeatureDocuSeal (self-hosted)Build From ScratchEffort if Scratch
PDF rendering✓ DoneYou build4–5 days
Field placement UI✓ DoneYou build3–4 days
Signature capture✓ Donesignature_pad (easy)1–2 days
PDF finalization✓ Donepdf-lib (complex)3–4 days
Signer portal✓ DoneJWT + no-auth page2 days
Multi-signer + order✓ DoneState machine2 days
Audit trail✓ Done (legal)Hash chain logic2 days
Email notifications✓ DoneResend (easy)1 day
Webhooks✓ Built-inCustom relay1 day
REST API✓ Full APIYou build all routes2–3 days
Templates✓ DoneTemplate storage system1–2 days
Mobile signature UX✓ TestedTricky touch handling2–3 days extra
Embedded iframe signing✓ Built-inCORS + security config1 day
Branded dashboardYou build on topYou build (same effort)Same either way
Stripe billingYou buildYou buildSame either way

✅ The Right Strategy

DocuSeal handles ~80% of the hardest work (PDF engine, signing UI, legal compliance). Building from scratch is absolutely doable — but it's 3–4 weeks vs 4–5 days, with most of that time on problems DocuSeal already solved years ago.

Now — Ship Fast

White-label DocuSeal. Launch Signnee in a week. Get paying clients. The money validates the idea before spending weeks building the engine.

Later — Own It (Optional)

If Signnee hits 50+ clients and $2K+ MRR, spend a sprint migrating to the scratch-built engine. Revenue funds the rebuild. No risk taken upfront.