Skip to content

Latest commit

 

History

History
405 lines (310 loc) · 17.7 KB

File metadata and controls

405 lines (310 loc) · 17.7 KB

KeepContracts Architecture

This document provides a high-level overview of the KeepContracts (KC) codebase to help humans and agents understand how the application is structured.


KeepContracts Product Overview

What KeepContracts is: A document signing engine (white-labeled Documenso), running at keepcontracts.com. It is invisible infrastructure. End users never interact with it directly. Signup is disabled and only DataThink admins have accounts.

What is an Envelope

An envelope is a single signing transaction. It holds a document, a list of recipients, and tracks status.

  • 1 contractor signing an NDA → 1 envelope
  • 1 teacher sending a permission slip to 30 parents → 30 envelopes (one per parent)

How Integration Works

1. DataThink apps (i.e. Ladderly or KeepHours) calls KC API using a service account key
2. KC creates the envelope and emails the recipient a magic link
3. Recipient clicks the link and signs (no KC account needed)
4. KC webhooks back to the DataThink app on completion
5. DataThink app downloads and permanently stores the signed PDF
6. KC is done. The DataThink app owns the record

How Multiple Documents Get Connected to the Same Person

KC doesn't do this, the DataThink app does. When KC creates an envelope it returns an envelope_id. Your app stores that ID linked to the relevant person in your own database:

contractor_id envelope_id document sent_at
99 kc-envelope-abc NDA Jan 15
99 kc-envelope-xyz W9 Form Feb 15

WHERE contractor_id = 99 → full history, regardless of how many envelopes.

Use Cases

Ladderly: Teacher clicks "send" in Ladderly → Ladderly calls KC API → parent receives magic link → signs → KC webhooks to Ladderly → Ladderly stores signed PDF. Teacher sees all their documents in Ladderly filtered by teacher_id. Teacher never touches keepcontracts.com.

KeepHours: DataThink sends NDA to contractor via KC API → contractor signs via magic link → KC webhooks to KeepHours → KeepHours stores signed PDF. Contractor logs into KeepHours and sees only their own documents — scoped by contractor_id in KeepHours' DB.


Codebase Overview

This section covers the technical architecture of the KeepContracts codebase (forked from Documenso).

Overview

Documenso is an open-source document signing platform built as a monorepo using npm workspaces and Turborepo. The application enables users to create, send, and sign documents electronically.

┌─────────────────────────────────────────────────────────────────────────────┐
│                              Remix App (Hono Server)                        │
│                                 apps/remix                                  │
├─────────────┬─────────────┬─────────────┬─────────────┬─────────────────────┤
│  /api/v1/*  │  /api/v2/*  │ /api/trpc/* │ /api/jobs/* │   React Router UI   │
│  (ts-rest)  │   (tRPC)    │   (tRPC)    │  (Jobs API) │                     │
├─────────────┴─────────────┴─────────────┴─────────────┴─────────────────────┤
│                                                                             │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────────────┐    │
│  │  @api   │  │  @trpc  │  │  @lib   │  │  @email │  │    @signing     │    │
│  │ (REST)  │  │  (RPC)  │  │  (CORE) │  │         │  │                 │    │
│  └─────────┘  └─────────┘  └────┬────┘  └─────────┘  └─────────────────┘    │
│                                 │                                           │
│              ┌──────────────────┼──────────────────┐                        │
│              │                  │                  │                        │
│         ┌────▼────┐       ┌─────▼─────┐      ┌─────▼─────┐                  │
│         │ Storage │       │   Jobs    │      │    PDF    │                  │
│         │Provider │       │  Provider │      │  Signing  │                  │
│         └────┬────┘       └─────┬─────┘      └─────┬─────┘                  │
│              │                  │                  │                        │
└──────────────┼──────────────────┼──────────────────┼────────────────────────┘
               │                  │                  │
        ┌──────┴──────┐    ┌──────┴──────┐    ┌──────┴──────┐
        │  Database   │    │   Inngest/  │    │ Google KMS/ │
        │     S3      │    │    Local    │    │    Local    │
        └─────────────┘    └─────────────┘    └─────────────┘

Monorepo Structure

Applications (apps/)

Package Description Port
@documenso/remix Main application - React Router (Remix) with Hono server 3000
@documenso/documentation Documentation site (Next.js + Nextra) 3002
@documenso/openpage-api Public analytics API 3003

Core Packages (packages/)

Package Description
@documenso/lib Core business logic (server-only, client-only, universal)
@documenso/trpc tRPC API layer with OpenAPI support (API V2)
@documenso/api REST API layer using ts-rest (API V1)
@documenso/prisma Database layer (Prisma ORM + Kysely)
@documenso/ui UI component library (Shadcn + Radix + Tailwind)
@documenso/email Email templates and mailer (React Email)
@documenso/auth Authentication (OAuth via Arctic, WebAuthn/Passkeys)
@documenso/signing PDF signing (Local P12, Google Cloud KMS)
@documenso/ee Enterprise Edition features
@documenso/assets Static assets

Supporting Packages

Package Description
@documenso/app-tests E2E tests (Playwright)
@documenso/tailwind-config Shared Tailwind config
@documenso/tsconfig Shared TypeScript configs

Tech Stack

Category Technology
Frontend React 18, React Router v7 (Remix)
Server Hono
Database PostgreSQL 15, Prisma, Kysely
API tRPC, ts-rest, OpenAPI
Styling Tailwind CSS, Radix UI, Shadcn UI
Auth Arctic (OAuth), WebAuthn/Passkeys
Email React Email, Nodemailer
Jobs Inngest / Local
Storage S3-compatible / Database
PDF @libpdf/core, pdfjs-dist
i18n Lingui
Build Turborepo, Vite
Testing Playwright

API Architecture

API V1 (Deprecated)

  • Location: packages/api/v1/
  • Framework: ts-rest (contract-based REST)
  • Mount: /api/v1/*
  • Auth: API Token (Bearer header)
  • Status: Deprecated but maintained

Routes (RESTful pattern):

  • GET/POST/DELETE /api/v1/documents/*
  • GET/POST/DELETE /api/v1/templates/*
  • Recipients and fields nested under documents

API V2 (Current)

  • Location: packages/trpc/server/
  • Framework: tRPC with trpc-to-openapi
  • Mount: /api/v2/*, /api/v2-beta/*
  • Auth: API Token or Session Cookie
  • Status: Active

Routes (action-based pattern):

  • GET/POST /api/v2/document/* - Document operations
  • GET/POST /api/v2/template/* - Template operations
  • GET/POST /api/v2/envelope/* - Envelope operations (multi-document)
  • GET/POST /api/v2/folder/* - Folder management

Route Organization:

packages/trpc/server/
├── document-router/
│   ├── get-document.ts
│   ├── get-document.types.ts
│   └── ...
├── template-router/
├── envelope-router/
├── recipient-router/
├── field-router/
└── ...

Internal tRPC API

  • Mount: /api/trpc/*
  • Usage: Frontend-to-backend communication
  • Auth: Session-based

Background Jobs

Jobs handle async operations like email sending, document sealing, and webhooks.

Architecture

┌─────────────────┐     ┌───────────────────────────────────────┐
│ triggerJob()    │────▶│         Job Provider                  │
│                 │     │  ┌─────────────┬─────────────────┐    │
│ - name          │     │  │   Inngest   │      Local      │    │
│ - payload       │     │  │   (Cloud)   │   (Database)    │    │
└─────────────────┘     │  └─────────────┴─────────────────┘    │
                        │                │                      │
                        │                ▼                      │
                        │    ┌─────────────────────┐            │
                        │    │  Job Handler        │            │
                        │    │  (async processing) │            │
                        │    └─────────────────────┘            │
                        └───────────────────────────────────────┘

Location

  • packages/lib/jobs/client/ - Provider implementations
  • packages/lib/jobs/definitions/ - Job definitions

Job Types

Email Jobs:

  • send.signing.requested.email - Signing invitation
  • send-confirmation-email - Email verification
  • send-recipient-signed-email - Notify on signature
  • send-rejection-emails - Rejection notifications
  • send-document-cancelled-emails - Cancellation notices

Internal Jobs:

  • internal.seal-document - Finalize signed documents
  • internal.bulk-send-template - Bulk document sending
  • internal.execute-webhook - External webhook calls

Swappable Providers

The codebase uses a strategy pattern with ts-pattern for provider selection via environment variables.

Storage Provider

Handles file uploads and downloads.

Provider Description Env Value
Database Store files as Base64 in DB database
S3 S3-compatible storage (+ CloudFront) s3

Config: NEXT_PUBLIC_UPLOAD_TRANSPORT

Location: packages/lib/universal/upload/

PDF Signing Provider

Cryptographically signs PDF documents.

Provider Description Env Value
Local P12 certificate file local
Google Cloud HSM Google Cloud KMS gcloud-hsm

Config: NEXT_PRIVATE_SIGNING_TRANSPORT

Location: packages/signing/

Email Provider

Sends transactional emails.

Provider Description Env Value
SMTP Auth Standard SMTP with credentials smtp-auth
SMTP API SMTP with API key smtp-api
Resend Resend API resend
MailChannels MailChannels API mailchannels

Config: NEXT_PRIVATE_SMTP_TRANSPORT

Location: packages/email/mailer.ts

Background Jobs Provider

Processes async jobs.

Provider Description Env Value
Local Database-backed queue local (default)
BullMQ Redis-backed queue bullmq
Inngest Managed cloud service inngest

Config: NEXT_PRIVATE_JOBS_PROVIDER

Location: packages/lib/jobs/client/

Request Flow

Web Application Request

Browser
   │
   ▼
Hono Server (apps/remix/server/)
   │
   ├──▶ /api/v1/* ──▶ ts-rest handlers (packages/api/)
   │
   ├──▶ /api/v2/* ──▶ tRPC OpenAPI handlers (packages/trpc/)
   │
   ├──▶ /api/trpc/* ──▶ tRPC handlers (packages/trpc/)
   │
   ├──▶ /api/jobs/* ──▶ Job handlers (packages/lib/jobs/)
   │
   └──▶ /* ──▶ React Router (apps/remix/app/routes/)
                    │
                    ▼
              React Components (packages/ui/)

Document Signing Flow

1. Upload Document ──▶ Storage Provider (DB/S3)
                                  │
2. Add Recipients ────────────────┤
                                  │
3. Add Fields ────────────────────┤
                                  │
4. Send Document ─────────────────┤
       │                          │
       ▼                          │
   Email Job ──▶ Email Provider   |
       │                          |
5. Recipient Signs ───────────────┤
       │                          │
       ▼                          │
   seal-document Job              │
       │                          │
       ▼                          │
   Signing Provider ◀─────────────┘
       │
       ▼
   Signed PDF ──▶ Storage Provider

Key Directories

documenso/
├── apps/
│   └── remix/
│       ├── app/
│       │   └── routes/           # React Router routes
│       │       ├── _authenticated+/  # Protected routes
│       │       ├── _unauthenticated+/  # Public routes
│       │       └── _recipient+/  # Signing routes
│       └── server/
│           ├── router.ts         # Hono route mounting
│           └── main.js           # Entry point
├── packages/
│   ├── api/v1/                   # API V1 (ts-rest)
│   ├── trpc/server/              # API V2 + Internal (tRPC)
│   ├── lib/
│   │   ├── server-only/          # Server business logic
│   │   ├── client-only/          # Client utilities
│   │   ├── universal/            # Shared code
│   │   └── jobs/                 # Background jobs
│   ├── prisma/                   # Database schema & client
│   ├── signing/                  # PDF signing
│   ├── email/                    # Email templates
│   └── ui/                       # Component library
└── docker/                       # Docker configs

Development

# Full setup (install, docker, migrate, seed, dev)
npm run d

# Start development server
npm run dev

# Database GUI
npm run prisma:studio

# Type checking (faster than build)
npx tsc --noEmit

# E2E tests
npm run test:e2e

Docker Services (Development)

Service Port
PostgreSQL 54320
Inbucket (Mail) 9000
MinIO (S3) 9001, 9002

Environment Variables Summary

Variable Purpose Options
NEXT_PUBLIC_UPLOAD_TRANSPORT Storage provider database, s3
NEXT_PRIVATE_SIGNING_TRANSPORT Signing provider local, gcloud-hsm
NEXT_PRIVATE_SMTP_TRANSPORT Email provider smtp-auth, smtp-api, resend, mailchannels
NEXT_PRIVATE_JOBS_PROVIDER Jobs provider local, inngest

See .env.example for the complete list of configuration options.