documenso-multi-env-setup
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". allowed-tools: Read, Write, Edit 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)
Installation
This skill is included in the documenso-pack plugin:
/plugin install documenso-pack@claude-code-plugins-plus
Click to copy
Instructions
# 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
```typescript
// 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
```typescript
// src/config/documenso.ts
import { z } from "zod";
const EnvironmentSchema = z.enum(["development", "staging", "production"]);
type Environment = z.infer;
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;
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
```bash
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
```bash
NODE_ENV=staging
DOCUMENSO_API_KEY_STAGING=dcs_staging_xxx
DOCUMENSO_WEBHOOK_SECRET=staging-webhook-secret
```
### .env.production
```bash
NODE_ENV=production
DOCUMENSO_API_KEY_PRODUCTION=dcs_prod_xxx
DOCUMENSO_WEBHOOK_SECRET=prod-webhook-secret
```
## Client Factory
```typescript
// 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();
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
```typescript
// 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
```typescript
// 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
```typescript
// 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 = {
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
```typescript
// 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
| Issue | Cause | Solution |
|-------|-------|----------|
| Wrong environment | Missing NODE_ENV | Set explicitly |
| Key mismatch | Using wrong key | Check env var names |
| Config not loading | File path wrong | Verify config files |
| Mock not working | mockEnabled: false | Enable in dev config |
## Resources
- [12-Factor App Config](https://12factor.net/config)
- [Environment Management](https://docs.documenso.com/developers)
- [Secret Management Best Practices](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html)
## Next Steps
For monitoring setup, see `documenso-observability`.