CtrlK
CommunityDocumentationLog inGet started
Tessl Logo

documenso-multi-env-setup

tessl install github:jeremylongshore/claude-code-plugins-plus-skills --skill documenso-multi-env-setup
github.com/jeremylongshore/claude-code-plugins-plus-skills

Configure Documenso across multiple environments (dev, staging, production). Use when setting up environment-specific configurations, managing API keys, or implementing environment promotion workflows. Trigger with phrases like "documenso environments", "documenso staging", "documenso dev setup", "multi-environment documenso".

Review Score

78%

Validation Score

12/16

Implementation Score

65%

Activation Score

90%

Documenso Multi-Environment Setup

Overview

Configure and manage Documenso integrations across development, staging, and production environments with proper isolation and promotion workflows.

Prerequisites

  • Documenso accounts for each environment (or self-hosted instances)
  • Environment management infrastructure
  • Secret management solution (Vault, AWS Secrets Manager, etc.)

Environment Architecture

┌─────────────────────────────────────────────────────────┐
│                    Development                           │
│  API: stg-app.documenso.com (staging)                   │
│  Key: DOCUMENSO_API_KEY_DEV                             │
│  Purpose: Local development, testing                     │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                      Staging                             │
│  API: stg-app.documenso.com                             │
│  Key: DOCUMENSO_API_KEY_STAGING                         │
│  Purpose: Integration testing, QA                        │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                     Production                           │
│  API: app.documenso.com                                 │
│  Key: DOCUMENSO_API_KEY_PRODUCTION                      │
│  Purpose: Live customer traffic                          │
└─────────────────────────────────────────────────────────┘

Configuration Files

Environment-Specific Configs

// config/documenso.development.json
{
  "baseUrl": "https://stg-app.documenso.com/api/v2/",
  "timeout": 30000,
  "debug": true,
  "mockEnabled": true,
  "testRecipients": ["dev@yourcompany.com"],
  "cacheOptions": {
    "enabled": false,
    "ttl": 60
  }
}

// config/documenso.staging.json
{
  "baseUrl": "https://stg-app.documenso.com/api/v2/",
  "timeout": 30000,
  "debug": true,
  "mockEnabled": false,
  "testRecipients": ["staging-test@yourcompany.com"],
  "cacheOptions": {
    "enabled": true,
    "ttl": 300
  }
}

// config/documenso.production.json
{
  "baseUrl": "https://app.documenso.com/api/v2/",
  "timeout": 30000,
  "debug": false,
  "mockEnabled": false,
  "testRecipients": [],
  "cacheOptions": {
    "enabled": true,
    "ttl": 600
  }
}

Configuration Loader

// src/config/documenso.ts
import { z } from "zod";

const EnvironmentSchema = z.enum(["development", "staging", "production"]);
type Environment = z.infer<typeof EnvironmentSchema>;

const ConfigSchema = z.object({
  baseUrl: z.string().url(),
  timeout: z.number().default(30000),
  debug: z.boolean().default(false),
  mockEnabled: z.boolean().default(false),
  testRecipients: z.array(z.string().email()).default([]),
  cacheOptions: z.object({
    enabled: z.boolean(),
    ttl: z.number(),
  }),
});

export type DocumensoConfig = z.infer<typeof ConfigSchema>;

export function getEnvironment(): Environment {
  const env = process.env.NODE_ENV ?? "development";
  return EnvironmentSchema.parse(env);
}

export function loadConfig(): DocumensoConfig & { apiKey: string } {
  const env = getEnvironment();

  // Load file config
  const fileConfig = require(`../../config/documenso.${env}.json`);

  // Get API key from environment variable
  const apiKeyVar = `DOCUMENSO_API_KEY_${env.toUpperCase()}`;
  const apiKey = process.env[apiKeyVar] ?? process.env.DOCUMENSO_API_KEY;

  if (!apiKey) {
    throw new Error(`Missing API key: ${apiKeyVar}`);
  }

  // Allow environment variable overrides
  const config = {
    ...ConfigSchema.parse(fileConfig),
    apiKey,
    baseUrl: process.env.DOCUMENSO_BASE_URL ?? fileConfig.baseUrl,
  };

  return config;
}

Environment Variables

.env.development

NODE_ENV=development
DOCUMENSO_API_KEY_DEVELOPMENT=dcs_dev_xxx
# Or use alias
DOCUMENSO_API_KEY=${DOCUMENSO_API_KEY_DEVELOPMENT}

# Optional overrides
DOCUMENSO_BASE_URL=https://stg-app.documenso.com/api/v2/
DOCUMENSO_WEBHOOK_SECRET=dev-webhook-secret

.env.staging

NODE_ENV=staging
DOCUMENSO_API_KEY_STAGING=dcs_staging_xxx
DOCUMENSO_WEBHOOK_SECRET=staging-webhook-secret

.env.production

NODE_ENV=production
DOCUMENSO_API_KEY_PRODUCTION=dcs_prod_xxx
DOCUMENSO_WEBHOOK_SECRET=prod-webhook-secret

Client Factory

// src/documenso/factory.ts
import { Documenso } from "@documenso/sdk-typescript";
import { loadConfig, getEnvironment } from "../config/documenso";

interface ClientOptions {
  forceEnvironment?: "development" | "staging" | "production";
  mockMode?: boolean;
}

const clients = new Map<string, Documenso>();

export function getDocumensoClient(options?: ClientOptions): Documenso {
  const env = options?.forceEnvironment ?? getEnvironment();
  const cacheKey = `${env}-${options?.mockMode ?? false}`;

  if (clients.has(cacheKey)) {
    return clients.get(cacheKey)!;
  }

  const config = loadConfig();

  // Use mock client in development if enabled
  if (options?.mockMode && config.mockEnabled) {
    console.log(`[Documenso] Using mock client for ${env}`);
    return createMockClient();
  }

  const client = new Documenso({
    apiKey: config.apiKey,
    serverURL: config.baseUrl,
    timeoutMs: config.timeout,
    debugLogger: config.debug ? console : undefined,
  });

  clients.set(cacheKey, client);

  console.log(`[Documenso] Client initialized for ${env}`);
  console.log(`[Documenso] Base URL: ${config.baseUrl}`);

  return client;
}

// Reset clients (useful for testing)
export function resetClients(): void {
  clients.clear();
}

Mock Client for Development

// src/documenso/mock.ts
import { Documenso } from "@documenso/sdk-typescript";

let mockDocumentCounter = 0;

export function createMockClient(): Documenso {
  // Create a proxy that intercepts all method calls
  const mockClient = {
    documents: {
      createV0: async (input: any) => {
        mockDocumentCounter++;
        console.log(`[MOCK] Creating document: ${input.title}`);
        return {
          documentId: `mock_doc_${mockDocumentCounter}`,
          title: input.title,
          status: "DRAFT",
        };
      },
      getV0: async ({ documentId }: any) => {
        console.log(`[MOCK] Getting document: ${documentId}`);
        return {
          id: documentId,
          title: "Mock Document",
          status: "PENDING",
          recipients: [],
        };
      },
      findV0: async () => {
        console.log(`[MOCK] Finding documents`);
        return { documents: [], totalPages: 0 };
      },
      deleteV0: async ({ documentId }: any) => {
        console.log(`[MOCK] Deleting document: ${documentId}`);
        return { success: true };
      },
    },
    templates: {
      getV0: async ({ templateId }: any) => {
        console.log(`[MOCK] Getting template: ${templateId}`);
        return {
          id: templateId,
          title: "Mock Template",
          recipients: [],
        };
      },
    },
    // Add more mock methods as needed
  };

  return mockClient as unknown as Documenso;
}

Environment Promotion

// scripts/promote-templates.ts
/**
 * Promote templates from staging to production.
 * Templates are recreated in production with same configuration.
 */

import { getDocumensoClient } from "../src/documenso/factory";

interface TemplateMapping {
  stagingId: string;
  productionId?: string;
  name: string;
}

const TEMPLATE_MAPPINGS: TemplateMapping[] = [
  { stagingId: "tmpl_stg_nda", name: "NDA Template" },
  { stagingId: "tmpl_stg_contract", name: "Contract Template" },
];

async function promoteTemplates() {
  const stagingClient = getDocumensoClient({ forceEnvironment: "staging" });
  const prodClient = getDocumensoClient({ forceEnvironment: "production" });

  console.log("Promoting templates from staging to production...\n");

  for (const mapping of TEMPLATE_MAPPINGS) {
    try {
      // Get staging template
      const stagingTemplate = await stagingClient.templates.getV0({
        templateId: mapping.stagingId,
      });

      console.log(`Template: ${mapping.name}`);
      console.log(`  Staging ID: ${mapping.stagingId}`);

      // Note: In practice, you'd need to download the PDF and recreate
      // the template in production. This is a simplified example.
      console.log(`  Status: Ready for manual creation in production`);
      console.log(`  Fields: ${stagingTemplate.fields?.length ?? 0}`);
      console.log(`  Recipients: ${stagingTemplate.recipients?.length ?? 0}`);
      console.log("");
    } catch (error: any) {
      console.error(`  Error: ${error.message}`);
    }
  }
}

promoteTemplates().catch(console.error);

Webhook Configuration Per Environment

// src/webhooks/config.ts
import { getEnvironment } from "../config/documenso";

interface WebhookConfig {
  url: string;
  secret: string;
  events: string[];
}

export function getWebhookConfig(): WebhookConfig {
  const env = getEnvironment();

  const configs: Record<string, WebhookConfig> = {
    development: {
      url: "https://dev.yourapp.com/webhooks/documenso",
      secret: process.env.DOCUMENSO_WEBHOOK_SECRET_DEV ?? "",
      events: ["document.created", "document.completed"],
    },
    staging: {
      url: "https://staging.yourapp.com/webhooks/documenso",
      secret: process.env.DOCUMENSO_WEBHOOK_SECRET_STAGING ?? "",
      events: [
        "document.created",
        "document.sent",
        "document.signed",
        "document.completed",
      ],
    },
    production: {
      url: "https://app.yourapp.com/webhooks/documenso",
      secret: process.env.DOCUMENSO_WEBHOOK_SECRET_PRODUCTION ?? "",
      events: [
        "document.created",
        "document.sent",
        "document.opened",
        "document.signed",
        "document.completed",
        "document.rejected",
        "document.cancelled",
      ],
    },
  };

  return configs[env];
}

Testing Across Environments

// tests/integration/environment.test.ts
import { describe, it, expect, beforeAll } from "vitest";
import { getDocumensoClient, resetClients } from "../../src/documenso/factory";

describe("Multi-Environment Configuration", () => {
  beforeAll(() => {
    resetClients();
  });

  it("connects to staging environment", async () => {
    const client = getDocumensoClient({ forceEnvironment: "staging" });
    const result = await client.documents.findV0({ perPage: 1 });
    expect(result).toBeDefined();
  });

  it("uses mock client in development with mockMode", async () => {
    const client = getDocumensoClient({
      forceEnvironment: "development",
      mockMode: true,
    });
    const doc = await client.documents.createV0({ title: "Test" });
    expect(doc.documentId).toMatch(/^mock_doc_/);
  });

  it("production requires production API key", async () => {
    // This test verifies production config is properly isolated
    const config = loadConfigForEnv("production");
    expect(config.baseUrl).toBe("https://app.documenso.com/api/v2/");
    expect(config.debug).toBe(false);
  });
});

Environment Checklist

Development

  • Using staging Documenso API
  • Mock mode available for offline development
  • Debug logging enabled
  • Test recipients configured
  • Caching disabled for fresh data

Staging

  • Separate API key from production
  • All webhooks configured
  • Integration tests passing
  • Template parity with production
  • Monitoring configured

Production

  • Production API key secured
  • Webhooks verified
  • Caching enabled
  • Debug logging disabled
  • Alerting configured

Output

  • Environment-specific configurations
  • Isolated API keys per environment
  • Promotion workflow documented
  • Mock client for development

Error Handling

IssueCauseSolution
Wrong environmentMissing NODE_ENVSet explicitly
Key mismatchUsing wrong keyCheck env var names
Config not loadingFile path wrongVerify config files
Mock not workingmockEnabled: falseEnable in dev config

Resources

  • 12-Factor App Config
  • Environment Management
  • Secret Management Best Practices

Next Steps

For monitoring setup, see documenso-observability.