buildyourown.software

E-signature · by Docusign, Inc.

Build your own DocuSign.

Upload a PDF, drop a signature box, email it, get it back signed with an audit trail. DocuSign meters that by the envelope and charges per user on top.

What you're paying now

$2,700 per year

Business Pro for 5 users: $45 × 5 × 12 on annual billing, capped at 100 envelopes per user per year.

Build it now

Build it in any tool

Paste into Claude Code, ChatGPT, Codex, Replit, Lovable, or Cursor. The agent fetches everything it needs.

Paste anywhere

Build my own DocuSign. Fetch https://buildyourown.software/like/docusign/llms.md and follow it: build the app from the build prompt, then write and run the tests from the test plan. Ask me before changing the data model or the non-goals.

Claude Code

claude "Build my own DocuSign. Fetch https://buildyourown.software/like/docusign/llms.md and follow it: build the app from the build prompt, then write and run the tests from the test plan. Ask me before changing the data model or the non-goals."

Codex CLI

codex "Build my own DocuSign. Fetch https://buildyourown.software/like/docusign/llms.md and follow it: build the app from the build prompt, then write and run the tests from the test plan. Ask me before changing the data model or the non-goals."
Raw files for agents:llms.mdbuild-prompt.mdtest-prompt.mdIf your tool can't open links, copy the full prompt in section 05 instead.

01

What it does

DocuSign lets you send a document to one or more people, have them sign it in a browser, and get a completed PDF back with a certificate that records who signed, when, and from where. That record is what makes the signature hold up.

The rest of the product is volume and enterprise: templates, bulk send, web forms, payments during signing, identity verification, CLM (contract lifecycle management), and integrations into Salesforce and the like.

Electronic signatures are legally valid in the US under the ESIGN Act and in the EU under eIDAS when you can show intent, consent, and a tamper-evident record. None of that requires DocuSign. It requires a PDF library, an email, and a log.

02

What it costs

List prices from the vendor's own pricing page. The fine print is where the money goes.

Personal

$11

per month billed annually ($132/yr), single user

  • 5 envelopes per month
  • Reusable templates
  • Basic fields

Five sends a month. Send a sixth and you're upgrading.

Standard

$30

per user / month billed annually ($360/user/yr)

  • 100 envelopes per user per year
  • Shared templates
  • Custom branding
  • Comments

Business Pro

$45

per user / month billed annually ($540/user/yr)

  • 100 envelopes per user per year
  • Web forms
  • Payments during signing
  • Bulk send
  • Formulas and conditional fields

Enhanced plans

Custom

50+ users, contact sales

  • Custom envelope allowances
  • SSO
  • Org admin
  • 24/7 support

The fine print

  • Envelopes are the real meter. 100 per user per year is about two a week; go over and you buy more or move up a tier.
  • Every person who needs to send needs a seat. Signers are free, senders are not.
  • Annual billing is the advertised price. Monthly billing costs more per month.
  • Identity verification, SMS delivery, and notarization are add-ons charged per use.
  • Your signed documents live in DocuSign. Getting them all out at once requires the API or a support ticket.

Prices checked September 10, 2026 at ecom.docusign.com. They change. Check before you quote them.

03

What you actually use

The headline features, and an honest call on whether a typical team needs each one.

8 of 12 headline features are worth building for a typical user. The rest is what you're paying for.

  • Build it

    Upload a document and place fields

    Drag signature, initials, date, text, and checkbox fields onto PDF pages for each signer.

    Why: This is the sender's whole experience. A PDF viewer with absolutely positioned boxes.

  • Build it

    Send to signers by email

    Each signer gets a unique link. Optional signing order.

    Why: A tokenized link per signer and one email. Sequential order is a status field.

  • Build it

    Sign in the browser

    Draw, type, or upload a signature; fill fields; agree to e-sign consent.

    Why: Canvas for drawing, a cursive font for typing. The consent checkbox is what makes it legal.

  • Build it

    Completed PDF

    Signatures and field values burned into the PDF, sent to everyone.

    Why: pdf-lib draws images and text at coordinates. This is the deliverable.

  • Build it

    Certificate of completion and audit trail

    Who signed, when, IP address, email, document hash, every event.

    Why: This is what makes the signature defensible. Append it as the last page and store the log.

  • Build it

    Reminders and expiration

    Nudge signers who haven't signed; void after a deadline.

    Why: A cron job and two columns.

  • Build it

    Templates

    Save a document with its fields to reuse with new signers.

    Why: If you send the same NDA weekly, this is most of the value. It's the same table with a flag.

  • Build it

    Status dashboard

    See what's out, who's signed, what's overdue.

    Why: One list view.

  • Maybe

    Bulk send

    Send one template to hundreds of recipients from a CSV.

    Why: Useful for HR or annual renewals. It's a loop over the send function; add it when you need it.

  • Skip

    Web forms and payments during signing

    Public forms that generate documents; collect a card while signing.

    Why: Two different products bolted on. Use Stripe Checkout separately if you need to charge.

  • Skip

    Identity verification and notarization

    ID scans, knowledge-based auth, remote online notary.

    Why: Regulated and expensive. If your documents need this, keep paying for it.

  • Skip

    CLM, Salesforce, and enterprise admin

    Contract lifecycle management, CRM-triggered sends, SSO.

    Why: Not a small-team problem. A webhook on completion covers the integration case.

04

How it works under the hood

The data model, the flows that matter, and the integrations you need. This is what the build prompt is based on.

Data model

Document (envelope)

id · owner_id · title · original_pdf_key · signed_pdf_key · status (draft | sent | completed | voided | expired) · signing_order (parallel | sequential) · message · expires_at · completed_at · sha256_original · sha256_signed

Store PDFs in object storage (S3, R2, Supabase Storage), never in the database.

Signer

id · document_id · name · email · order · status (pending | viewed | signed | declined) · token · signed_at · ip · user_agent · consent_at · signature_image_key

token is a long random secret; the signing link is /sign/<token>.

Field

id · document_id · signer_id · type (signature | initials | date | text | checkbox) · page · x · y · width · height · required · value · label

Coordinates in PDF points from the bottom-left, matching pdf-lib.

Audit event

id · document_id · signer_id (nullable) · type (created | sent | viewed | consented | field_filled | signed | declined | reminded | completed | voided | downloaded) · at · ip · user_agent · meta (json)

Append-only. Never update or delete.

Template

id · owner_id · title · pdf_key · roles (json: [{ name, order }]) · fields (json, keyed by role)

Key flows

Prepare and send

  1. 1.Owner uploads a PDF. Server stores it, computes SHA-256, renders page thumbnails with pdf.js.
  2. 2.Owner adds signers (name, email, order) and drags fields onto pages for each signer.
  3. 3.On send: status → sent, a token per signer, audit 'sent'.
  4. 4.Email each signer in the first order group with their link. Later groups wait.

Sign

  1. 1.Signer opens /sign/<token>. Audit 'viewed' with IP and user agent.
  2. 2.Show the PDF with only their fields highlighted. Require the e-sign consent checkbox before any field is active.
  3. 3.Signer draws, types, or uploads a signature once; it's applied to every signature field. Fill remaining fields.
  4. 4.On submit: validate required fields, save values and signature image, audit 'signed', status → signed.
  5. 5.If sequential and more signers remain, email the next group. If everyone has signed, run completion.

Complete

  1. 1.Load the original PDF with pdf-lib. Draw each signature image and field value at its coordinates.
  2. 2.Append a certificate page: document title, original hash, each signer's name, email, IP, timestamps, and the full audit log.
  3. 3.Save, compute SHA-256 of the signed PDF, store both hashes, status → completed.
  4. 4.Email the signed PDF to the owner and every signer. Fire the completion webhook if configured.

Remind, expire, void

  1. 1.Daily cron: for sent documents, email pending signers every N days (owner setting), audit 'reminded'.
  2. 2.If expires_at has passed, status → expired, notify the owner.
  3. 3.Owner can void a sent document at any time; signing links stop working, audit 'voided'.

Integrations

  • pdf-librequiredDraw signatures and text into the PDF, append the certificate page.
  • pdf.js (pdfjs-dist)requiredRender pages in the browser for field placement and signing.
  • Object storage (S3, R2, or Supabase Storage)requiredOriginal and signed PDFs, signature images. Private buckets with signed URLs.
  • Resend (or Postmark)requiredSigning invitations, reminders, completed copies.
  • PostgresrequiredDocuments, signers, fields, audit log.
  • AuthrequiredOwner sign-in. Signers never need an account.
  • CronrequiredReminders and expiration.
  • Outbound webhookoptionalNotify your CRM or Slack on completion.
  • StripeoptionalIf you must collect payment alongside a signature, do it as a separate Checkout step.

05

The build prompt

The full spec the one-line command points at. Paste it directly if your tool can't fetch URLs, or edit the data model and non-goals first to fit your team.

docusign-build-prompt.md · 107 lines · 10,789 chars
# Build an e-signature app (replacing DocuSign eSignature)

You are building a self-hosted e-signature app for a small team. It replaces DocuSign for sending PDFs to be signed, collecting signatures in the browser, and producing a completed PDF with a certificate of completion and a tamper-evident audit trail. Build it end to end. The audit trail and the final PDF must be correct; everything else can be plain.

## Stack

- Next.js (App Router) with TypeScript, Tailwind
- Postgres (Supabase or Neon) with Drizzle or Prisma
- Object storage for PDFs and signature images: Supabase Storage, Cloudflare R2, or S3. Private bucket, short-lived signed URLs.
- `pdf-lib` for writing into PDFs; `pdfjs-dist` for rendering pages in the browser
- Resend for email
- Auth for owners only (magic link or Google). Signers never create accounts.
- A cron endpoint for reminders and expiration.

Use the repo's existing stack if there is one.

## Data model

All tables: `id` uuid, `created_at`, `updated_at`.

- `owners`: `email` (unique), `name`, `company_name`, `reminder_every_days` (default 3), `webhook_url` (nullable), `webhook_secret`.
- `documents`: `owner_id`, `title`, `message` (text to signers), `original_key` (storage path), `signed_key` (nullable), `original_sha256`, `signed_sha256` (nullable), `page_count`, `page_sizes` (jsonb: `[{ width, height }]` in PDF points), `status` (`draft` | `sent` | `completed` | `voided` | `expired`), `signing_order` (`parallel` | `sequential`), `expires_at` (nullable), `sent_at`, `completed_at`, `template_id` (nullable).
- `signers`: `document_id`, `name`, `email`, `order` (int, 1-based), `status` (`pending` | `viewed` | `signed` | `declined`), `token` (32 random bytes base64url, unique), `signature_image_key` (nullable), `signature_kind` (`drawn` | `typed` | `uploaded`), `consent_at`, `signed_at`, `declined_reason`, `last_ip`, `last_user_agent`.
- `fields`: `document_id`, `signer_id`, `type` (`signature` | `initials` | `date_signed` | `text` | `checkbox`), `page` (1-based), `x`, `y`, `width`, `height` (PDF points, origin bottom-left, matching pdf-lib), `required` (bool), `label`, `value` (nullable text), `font_size` (default 11).
- `audit_events`: `document_id`, `signer_id` (nullable), `type` (`created` | `uploaded` | `sent` | `viewed` | `consented` | `field_filled` | `signed` | `declined` | `reminded` | `completed` | `voided` | `expired` | `downloaded`), `at`, `ip`, `user_agent`, `meta` (jsonb). Append-only: no update or delete endpoints, and a database trigger that rejects UPDATE and DELETE.
- `templates`: `owner_id`, `title`, `pdf_key`, `page_count`, `page_sizes`, `roles` (jsonb `[{ name, order }]`), `fields` (jsonb, same shape as `fields` but with `role` instead of `signer_id`).

## Screens

### Owner (authenticated)

1. **Dashboard (`/app`)**: tabs for Action needed (sent, with any signer pending), Completed, Drafts, Voided/Expired. Each row: title, signers with status dots, sent date, expires date. Actions: remind, void, download signed PDF, view audit log.
2. **New document (`/app/new`)**: upload a PDF (max 25 MB). Server stores it, computes SHA-256, reads page count and sizes with pdf-lib, and creates a draft.
3. **Prepare (`/app/documents/[id]/prepare`)**: three-pane editor. Left: signers list (add name, email, drag to reorder, choose parallel or sequential). Center: pages rendered with pdf.js, scrollable. Right: field palette. Click a field type then click on a page to place it, assigned to the selected signer. Fields are draggable and resizable. Store coordinates converted to PDF points with the bottom-left origin. Show a "Send" button that validates every signer has at least one field and every signature field is assigned.
4. **Send modal**: subject, message, expiration (none, 7, 14, 30 days). On send: status → sent, generate tokens, write `sent` audit, email the first order group.
5. **Document detail (`/app/documents/[id]`)**: status, signers, full audit log table, download original and signed PDF, remind, void.
6. **Templates (`/app/templates`)**: create from a PDF with the same prepare editor but with roles instead of signers; "Use template" asks for a name and email per role and creates a prepared draft.
7. **Settings**: name, company, reminder cadence, webhook URL.

### Signer (public, no auth)

8. **Signing page (`/sign/[token]`)**: 
   - Reject with a clear page if the token is unknown, the document is voided or expired, or this signer already signed or declined.
   - Log a `viewed` audit event with IP and user agent on load; set signer status to `viewed`.
   - Show the document title, who sent it, and the message. Show the PDF pages with only this signer's fields overlaid, in page order, with a "next field" button.
   - Before any field is interactive, require a checkbox: "I agree to use electronic records and signatures" linking to a short consent page. Record `consented` with timestamp.
   - Signature capture modal: tabs for Draw (canvas, pointer events, works on touch), Type (name rendered in a cursive font, e.g. a Google Font like Dancing Script, rendered to PNG), Upload (PNG/JPG). Once captured, the same image is applied to every signature field; initials fields get a separate capture.
   - `date_signed` fields auto-fill with today's date in the signer's locale at submit time.
   - "Finish" validates required fields, uploads the signature PNG(s), saves field values, writes `field_filled` events (one per field, in `meta`) and a `signed` event, sets signer status to `signed`. Then run the "advance" logic below.
   - Also offer "Decline to sign" with a reason; records `declined`, emails the owner, and stops the document (status stays `sent` with a declined signer; owner can void).
9. **Done page**: confirmation, and once the document completes, a link to download the completed PDF (signed URL, logs `downloaded`).

## Advance and completion logic (server, `lib/documents/advance.ts`)

After any signer signs:
1. If `signing_order = sequential`, find the lowest `order` with any pending signer. If it's greater than the one that just signed, email that group and stop.
2. If any signer is still pending, stop.
3. Otherwise complete: 
   - Load the original PDF with pdf-lib.
   - For every field: `signature`/`initials` → `drawImage` of the PNG scaled to fit the box preserving aspect ratio, bottom-left aligned to (x, y). `text`/`date_signed` → `drawText` with Helvetica at `font_size`, clipped to width. `checkbox` → draw a check mark if true.
   - Append a certificate page (Letter or A4 matching page 1): title "Certificate of Completion", document title, document id, original SHA-256, sent date, completed date, then a table per signer with name, email, IP, consent time, signed time, signature kind; then the full audit log (event, who, when, IP), continuing onto more pages if needed.
   - Save, compute SHA-256, upload as `signed_key`, set `status = completed`, `completed_at`, write `completed` audit.
   - Email the completed PDF (attachment if under 10 MB, otherwise a signed link) to the owner and every signer.
   - If `webhook_url` is set, POST `{ event: "document.completed", document_id, title, signers, signed_sha256, download_url }` with an HMAC-SHA256 signature header using `webhook_secret`. Retry 3 times with backoff.

## Cron (`GET /api/cron/daily`, secret header)

- For each `sent` document with pending signers and `reminder_every_days` elapsed since the last `sent` or `reminded` event for that signer, email a reminder and write `reminded`.
- For each `sent` document with `expires_at < now`, set `expired`, write `expired`, email the owner.

## Security and integrity

- Signing tokens are unguessable (32 random bytes) and single-purpose. Never expose signer tokens to other signers or in owner-facing URLs.
- Storage bucket is private. All downloads go through signed URLs that expire in 10 minutes and log a `downloaded` event.
- Rate-limit `/sign/[token]` and the signature upload endpoint.
- Every state change writes an audit event in the same transaction.
- Store `x-forwarded-for` (first IP) and user agent on every signer action.
- Never mutate a completed document. Re-sending creates a new document.

## Non-goals

No bulk send, web forms, payments, identity verification, notarization, in-person signing, SMS delivery, Salesforce/CRM integration, or SSO. Keep the schema ready for `team_id` on owners.

## Acceptance criteria

1. Uploading a 3-page PDF creates a draft with `page_count = 3`, correct `page_sizes`, and a matching `original_sha256`.
2. A field placed at the visual center of page 2 in the editor is stored as PDF points within 2 pt of `(width/2 - w/2, height/2 - h/2)` for that page.
3. Sending a sequential document with signers A (order 1) and B (order 2) emails only A. After A signs, B is emailed. A parallel document emails both immediately.
4. Opening a signing link writes a `viewed` event with the request IP and user agent.
5. Fields are disabled until consent is checked; checking it writes `consented`.
6. Submitting with a required text field empty returns a validation error and writes nothing.
7. A drawn signature submitted from a touch device (Playwright mobile emulation) produces a non-empty PNG stored on the signer.
8. After the last signer signs, the completed PDF has `page_count + 1` (or more) pages, contains the signature image at the field's coordinates, and its SHA-256 equals `signed_sha256`.
9. The certificate page text includes every signer's name, email, IP, and the `signed` timestamp, and the audit table lists every event in order.
10. A voided document's signing links return the rejection page and write no `viewed` event.
11. The daily cron sends a reminder to a signer 3 days after `sent` and does not send again the next day; a document past `expires_at` becomes `expired` and the owner is emailed.
12. `UPDATE` or `DELETE` on `audit_events` fails at the database level.
13. Completion webhook is delivered with a valid HMAC signature and retried on a 500.
14. Creating a template from a PDF and using it with two named signers produces a draft with all fields assigned to the right signer.

## Deliverables

- The app with migrations, the audit trigger, and a seed script creating one owner, one template, one sent document with two signers, and one completed document.
- A `scripts/verify-pdf.ts` that takes a signed PDF and a document id and checks the hash and the certificate text.
- README: storage bucket setup, Resend setup, cron setup, environment variables, deployment.

Build in this order: upload and hashing, the prepare editor with correct coordinate conversion, sending and the signing page, completion with pdf-lib, the certificate page, cron, templates. Open the produced PDFs and check them visually after the completion step.

06

Where to build it

Any of these will take the prompt. Use the one you already pay for.

  • Claude CodeAgentic coding in your terminal

    Best fit. The PDF coordinate work and the audit trail need an agent that will write tests and open the output PDF to check it.

  • ReplitBuild and host in the browser

    Fine for the app. Use Replit's object storage for PDFs and its cron for reminders.

  • LovablePrompt-to-app with Supabase built in

    Good for the dashboard and signer UI. Put pdf-lib completion in a Supabase edge function.

  • ChatGPT / CodexPlan, then build with Codex

    Have ChatGPT adjust the field types and certificate wording to your jurisdiction, then build with Codex.

  • StripePayments, if you need them

    Only if you take payment with the signature. Do it as a separate step, not inside the signing page.

07

Test it

Walk the checklist by hand once, then let the agent write the automated tests.

Manual checklist

  • Upload a 3-page PDF and confirm all pages render with correct aspect ratio.
  • Place a signature field on page 2 near the bottom and confirm it lands in the same spot in the final PDF.
  • Send to two signers in sequential order and confirm the second does not get an email until the first signs.
  • Open a signing link and confirm no field is interactive until the consent box is checked.
  • Draw a signature on a phone and confirm it looks right in the completed PDF.
  • Type a signature and confirm the cursive rendering is applied to every signature field.
  • Leave a required field empty and confirm submission is blocked with a clear message.
  • Complete a document and confirm the certificate page lists every signer with IP and timestamps.
  • Confirm the SHA-256 of the downloaded signed PDF matches the value stored on the document.
  • Open a signing link after the document was voided and confirm it's rejected.
  • Set a 1-day expiration, advance the clock, and confirm the document expires and the owner is notified.
  • Decline as a signer and confirm the owner is emailed and the audit log records it.
  • Create a template from a completed document and send it to a new signer.

Generate automated tests

Paste this into the same tool that built the app. It writes tests against the acceptance criteria from the build prompt.

docusign-test-prompt.md · 33 lines · 3,280 chars
Write automated tests for the e-signature app in this repo, using the acceptance criteria below as the spec. Use Vitest with a real test Postgres for the integration tests, and Playwright (including a mobile emulation project) for signer flows. Mock Resend and the webhook target at the module boundary; use a local filesystem or MinIO adapter for storage in tests. Use `pdf-lib` and `pdf-parse` (or `pdfjs-dist`) in tests to inspect produced PDFs.

## Acceptance criteria to cover

1. Uploading `fixtures/three-pages.pdf` creates a draft with `page_count = 3`, correct `page_sizes`, and `original_sha256` equal to the file's hash.
2. A field placed at the visual center of page 2 is stored within 2 pt of the expected bottom-left PDF coordinates. Test the conversion function directly with a Letter page and an A4 landscape page.
3. Sequential send emails only the order-1 signer; after they sign, the order-2 signer is emailed. Parallel send emails all signers at once.
4. Opening `/sign/[token]` writes a `viewed` event with the IP from `x-forwarded-for` and the user agent.
5. Consent must be checked before fields are enabled; checking it writes `consented`.
6. Submitting with a required field empty returns 422 and writes no `signed` event and no field values.
7. A drawn signature on Playwright's mobile project results in a non-empty PNG in storage referenced by the signer.
8. After the final signer signs, the signed PDF has at least `page_count + 1` pages, contains an image XObject on page 2 near the field's coordinates, and its SHA-256 matches `signed_sha256`.
9. The certificate page text contains every signer's name, email, IP, and `signed` timestamp, and the audit table lists events in chronological order.
10. A voided document's signing link renders the rejection page and writes no `viewed` event.
11. Daily cron: a reminder is sent 3 days after `sent`, not again the next day; a document past `expires_at` becomes `expired` and the owner is emailed once.
12. `UPDATE` and `DELETE` on `audit_events` throw at the database level.
13. The completion webhook body carries a valid HMAC-SHA256 header and is retried when the endpoint returns 500 then 200.
14. Using a template with two roles produces a draft with fields assigned to the correct signers.
15. A signing token for a signer who already signed shows "already signed" and cannot re-submit.

## Layout

- `tests/unit/`: coordinate conversion, HMAC signing, certificate text builder, reminder-due logic (criteria 2, 11 logic, 13 signature).
- `tests/integration/`: upload, send, sign, advance, complete, cron, audit trigger, webhook (criteria 1, 3, 4, 6, 8, 9, 10, 11, 12, 13, 14, 15).
- `tests/e2e/`: Playwright desktop and mobile projects for 5 and 7, plus a full happy path from upload to download.

## Rules

- Name tests after criteria: `test("AC8: completed PDF hash matches signed_sha256")`.
- Put fixture PDFs in `fixtures/` (generate them with pdf-lib in a setup script if they don't exist: a 3-page Letter PDF and an A4 landscape PDF).
- Freeze time with fake timers for cron tests.
- Add `pnpm test` and a GitHub Actions workflow with a Postgres service.
- Run everything. Fix the app where it is wrong and the tests where they are wrong. Report per-criterion pass/fail and every change made.

DocuSign is a trademark of Docusign, Inc.. This page is independent research and is not affiliated with or endorsed by Docusign, Inc.. Last updated 2026-09-10.