πŸ“… let's chat! explore the endless possibilities creating industries that don't exist. click here

documenso-reference-architecture

Implement Documenso reference architecture with best-practice project layout. Use when designing new Documenso integrations, reviewing project structure, or establishing architecture standards for document signing applications. Trigger with phrases like "documenso architecture", "documenso best practices", "documenso project structure", "how to organize documenso". allowed-tools: Read, Grep version: 1.0.0 license: MIT author: Jeremy Longshore <jeremy@intentsolutions.io>

Allowed Tools

No tools specified

Provided by Plugin

documenso-pack

Claude Code skill pack for Documenso (24 skills)

saas packs v1.0.0
View Plugin

Installation

This skill is included in the documenso-pack plugin:

/plugin install documenso-pack@claude-code-plugins-plus

Click to copy

Instructions

# Documenso Reference Architecture ## Overview Production-ready architecture patterns for Documenso document signing integrations. ## Prerequisites - Understanding of layered architecture - Documenso SDK knowledge - TypeScript project setup - Testing framework configured ## Project Structure ``` my-signing-app/ β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ documenso/ β”‚ β”‚ β”œβ”€β”€ client.ts # Singleton client wrapper β”‚ β”‚ β”œβ”€β”€ config.ts # Environment configuration β”‚ β”‚ β”œβ”€β”€ types.ts # TypeScript types β”‚ β”‚ β”œβ”€β”€ errors.ts # Custom error classes β”‚ β”‚ └── handlers/ β”‚ β”‚ β”œβ”€β”€ webhooks.ts # Webhook handlers β”‚ β”‚ └── events.ts # Event processing β”‚ β”œβ”€β”€ services/ β”‚ β”‚ └── signing/ β”‚ β”‚ β”œβ”€β”€ index.ts # Service facade β”‚ β”‚ β”œβ”€β”€ documents.ts # Document operations β”‚ β”‚ β”œβ”€β”€ templates.ts # Template operations β”‚ β”‚ └── cache.ts # Caching layer β”‚ β”œβ”€β”€ api/ β”‚ β”‚ └── signing/ β”‚ β”‚ β”œβ”€β”€ routes.ts # API routes β”‚ β”‚ └── webhook.ts # Webhook endpoint β”‚ β”œβ”€β”€ jobs/ β”‚ β”‚ └── signing/ β”‚ β”‚ β”œβ”€β”€ cleanup.ts # Draft cleanup job β”‚ β”‚ └── sync.ts # Status sync job β”‚ └── utils/ β”‚ β”œβ”€β”€ pdf.ts # PDF utilities β”‚ └── validation.ts # Input validation β”œβ”€β”€ tests/ β”‚ β”œβ”€β”€ unit/ β”‚ β”‚ └── signing/ β”‚ β”‚ β”œβ”€β”€ documents.test.ts β”‚ β”‚ └── templates.test.ts β”‚ └── integration/ β”‚ └── signing/ β”‚ └── workflows.test.ts β”œβ”€β”€ config/ β”‚ β”œβ”€β”€ documenso.development.json β”‚ β”œβ”€β”€ documenso.staging.json β”‚ └── documenso.production.json β”œβ”€β”€ templates/ β”‚ └── pdf/ # PDF template files β”‚ β”œβ”€β”€ nda.pdf β”‚ β”œβ”€β”€ contract.pdf β”‚ └── agreement.pdf └── docs/ └── signing/ β”œβ”€β”€ SETUP.md └── RUNBOOK.md ``` ## Layer Architecture ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ API Layer β”‚ β”‚ (Controllers, Routes, Webhooks) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Service Layer β”‚ β”‚ (Business Logic, Orchestration) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Documenso Layer β”‚ β”‚ (Client, Types, Error Handling) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Infrastructure Layer β”‚ β”‚ (Cache, Queue, Monitoring) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ## Key Components ### Documenso Client Wrapper ```typescript // src/documenso/client.ts import { Documenso } from "@documenso/sdk-typescript"; import { DocumensoConfig, loadDocumensoConfig } from "./config"; import { CacheService } from "../services/signing/cache"; import { MetricsService } from "../utils/metrics"; export class DocumensoService { private client: Documenso; private cache: CacheService; private metrics: MetricsService; constructor(config: DocumensoConfig) { this.client = new Documenso({ apiKey: config.apiKey, serverURL: config.baseUrl, timeoutMs: config.timeout, }); this.cache = new CacheService(config.cacheOptions); this.metrics = new MetricsService("documenso"); } async getDocument(documentId: string) { return this.cache.getOrFetch( `doc:${documentId}`, () => this.metrics.track("getDocument", () => this.client.documents.getV0({ documentId }) ), { ttl: 300 } ); } async createDocument(input: CreateDocumentInput) { return this.metrics.track("createDocument", () => this.client.documents.createV0(input) ); } // Expose raw client for advanced use cases getRawClient(): Documenso { return this.client; } } // Singleton instance let service: DocumensoService | null = null; export function getDocumensoService(): DocumensoService { if (!service) { const config = loadDocumensoConfig(); service = new DocumensoService(config); } return service; } ``` ### Configuration Management ```typescript // src/documenso/config.ts import { z } from "zod"; const ConfigSchema = z.object({ apiKey: z.string().min(1), baseUrl: z.string().url().optional(), timeout: z.number().default(30000), cacheOptions: z.object({ enabled: z.boolean().default(true), ttl: z.number().default(300), }).default({}), }); export type DocumensoConfig = z.infer; export function loadDocumensoConfig(): DocumensoConfig { const env = process.env.NODE_ENV ?? "development"; // Load environment-specific config let fileConfig = {}; try { fileConfig = require(`../../config/documenso.${env}.json`); } catch { console.warn(`No config file for environment: ${env}`); } // Merge with environment variables const config = { apiKey: process.env.DOCUMENSO_API_KEY ?? "", baseUrl: process.env.DOCUMENSO_BASE_URL, ...fileConfig, }; return ConfigSchema.parse(config); } ``` ### Error Boundary ```typescript // src/documenso/errors.ts export class DocumensoServiceError extends Error { constructor( message: string, public readonly code: string, public readonly statusCode: number, public readonly retryable: boolean, public readonly originalError?: Error ) { super(message); this.name = "DocumensoServiceError"; } static fromSdkError(error: any): DocumensoServiceError { const statusCode = error.statusCode ?? error.status ?? 0; const retryable = statusCode === 429 || statusCode >= 500; return new DocumensoServiceError( error.message, `DOCUMENSO_${statusCode}`, statusCode, retryable, error ); } } export async function withErrorHandling( operation: () => Promise ): Promise { try { return await operation(); } catch (error) { throw DocumensoServiceError.fromSdkError(error); } } ``` ### Service Facade ```typescript // src/services/signing/index.ts import { getDocumensoService } from "../../documenso/client"; import { withErrorHandling } from "../../documenso/errors"; import { validateCreateDocumentInput } from "../../utils/validation"; export interface SigningService { createAndSendDocument(input: CreateDocumentInput): Promise; getDocumentStatus(documentId: string): Promise; downloadSignedDocument(documentId: string): Promise; } export class SigningServiceImpl implements SigningService { private documenso = getDocumensoService(); async createAndSendDocument( input: CreateDocumentInput ): Promise { // Validate input const validated = validateCreateDocumentInput(input); return withErrorHandling(async () => { // Create from template const envelope = await this.documenso.getRawClient().envelopes.useV0({ templateId: validated.templateId, title: validated.title, recipients: validated.recipients.map((r, i) => ({ email: r.email, name: r.name, signerIndex: i, })), }); // Send immediately await this.documenso.getRawClient().envelopes.distributeV0({ envelopeId: envelope.envelopeId!, }); return { documentId: envelope.envelopeId!, status: "SENT", }; }); } async getDocumentStatus(documentId: string): Promise { const doc = await this.documenso.getDocument(documentId); return { id: doc.id!, status: doc.status!, recipients: doc.recipients?.map((r) => ({ email: r.email!, status: r.signingStatus!, })) ?? [], }; } async downloadSignedDocument(documentId: string): Promise { return withErrorHandling(async () => { const result = await this.documenso.getRawClient().documents.downloadV0({ documentId, }); // Handle the download response return Buffer.from(result as any); }); } } // Export singleton let signingService: SigningService | null = null; export function getSigningService(): SigningService { if (!signingService) { signingService = new SigningServiceImpl(); } return signingService; } ``` ### Health Check ```typescript // src/documenso/health.ts import { getDocumensoService } from "./client"; export interface HealthStatus { status: "healthy" | "degraded" | "unhealthy"; latencyMs: number; error?: string; } export async function checkDocumensoHealth(): Promise { const service = getDocumensoService(); const start = Date.now(); try { await service.getRawClient().documents.findV0({ perPage: 1 }); return { status: "healthy", latencyMs: Date.now() - start, }; } catch (error: any) { return { status: "unhealthy", latencyMs: Date.now() - start, error: error.message, }; } } ``` ## Data Flow Diagram ``` User Request β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ API β”‚ β”‚ Layer β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Service │───▢│ Cache β”‚ β”‚ Layer β”‚ β”‚ (Redis) β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Documenso │───▢│ Queue β”‚ β”‚ Layer β”‚ β”‚ (Bull) β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Documenso β”‚ β”‚ API β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ## Webhook Architecture ``` Documenso β”‚ β–Ό POST /webhooks/documenso β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Webhook β”‚ β”‚ Handler β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Event β”‚ β”‚ Queue β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”œβ”€β”€β–Ά Update Database β”œβ”€β”€β–Ά Send Notifications β”œβ”€β”€β–Ά Trigger Workflows └──▢ Update Cache ``` ## Setup Script ```bash #!/bin/bash # scripts/setup-documenso-structure.sh # Create directory structure mkdir -p src/documenso/handlers mkdir -p src/services/signing mkdir -p src/api/signing mkdir -p src/jobs/signing mkdir -p src/utils mkdir -p tests/unit/signing mkdir -p tests/integration/signing mkdir -p config mkdir -p templates/pdf mkdir -p docs/signing # Create placeholder files touch src/documenso/{client,config,types,errors}.ts touch src/documenso/handlers/{webhooks,events}.ts touch src/services/signing/{index,documents,templates,cache}.ts touch src/api/signing/{routes,webhook}.ts touch config/documenso.{development,staging,production}.json echo "Documenso project structure created!" ``` ## Output - Structured project layout - Layered architecture implemented - Error handling configured - Health checks ready ## Error Handling | Issue | Cause | Solution | |-------|-------|----------| | Circular dependencies | Wrong layering | Separate by layer | | Config not loading | Wrong paths | Verify file locations | | Cache misses | Wrong keys | Check key generation | | Test isolation | Shared state | Use dependency injection | ## Resources - [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) - [Documenso SDK](https://github.com/documenso/sdk-typescript) - [TypeScript Project References](https://www.typescriptlang.org/docs/handbook/project-references.html) ## Next Steps For multi-environment setup, see `documenso-multi-env-setup`.

Skill file: plugins/saas-packs/documenso-pack/skills/documenso-reference-architecture/SKILL.md