Documenso Performance Tuning
Overview
Optimize Documenso integrations for speed and efficiency. Key strategies: reduce API round-trips with templates, cache document metadata, batch operations with concurrency control, and use async processing for bulk signing workflows.
Prerequisites
- Working Documenso integration
- Redis or in-memory cache (recommended)
- Completed
documenso-sdk-patterns setup
Instructions
Step 1: Reduce API Calls with Templates
The biggest performance win: templates reduce a multi-step document creation (create + upload + add recipients + add fields + send = 5+ calls) to just 2 calls (create from template + send).
async function createDocumentManual(signer: { email: string; name: string }) {
const doc = await client.documents.createV0({ title: "Contract" });
await client.documents.setFileV0(doc.documentId, { file: pdfBlob });
const recip = await client.documentsRecipients.createV0(doc.documentId, {
email: signer.email, name: signer.name, role: "SIGNER",
});
await client.documentsFields.createV0(doc.documentId, {
recipientId: recip.recipientId, type: "SIGNATURE",
pageNumber: 1, pageX: 10, pageY: 80, pageWidth: 30, pageHeight: 5,
});
await client.documents.sendV0(doc.documentId);
}
async function createDocumentFromTemplate(templateId: number, signer: { email: string; name: string }) {
const res = await fetch(
`${BASE}/templates/${templateId}/create-document`,
{
method: "POST",
headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
title: `Contract — ${signer.name}`,
recipients: [{ email: signer.email, name: signer.name, role: "SIGNER" }],
}),
}
);
const doc = await res.json();
await fetch(`${BASE}/documents/${doc.documentId}/send`, {
method: "POST",
headers: { Authorization: `Bearer ${API_KEY}` },
});
}
Step 2: Cache Document Metadata
import NodeCache from "node-cache";
const cache = new NodeCache({ stdTTL: 300, checkperiod: 60 });
export async function getCachedDocument(client: Documenso, documentId: number) {
const key = `doc:${documentId}`;
const cached = cache.get(key);
if (cached) return cached;
const doc = await client.documents.getV0(documentId);
if (doc.status === "COMPLETED") {
cache.set(key, doc, 3600);
} else {
cache.set(key, doc, 30);
}
return doc;
}
export function invalidateDocument(documentId: ) {
cache.();
}
Step 3: Batch Operations with Concurrency Control
import PQueue from "p-queue";
const queue = new PQueue({
concurrency: 5,
interval: 1000,
intervalCap: 10,
});
export async function batchCreateDocuments(
client: Documenso,
templateId: number,
signers: Array<{ email: string; name: string; company: string }>
): Promise<Array<{ email: string; documentId?: number; error?: string }>> {
const results = await Promise.allSettled(
signers.map((signer) =>
queue.add(async () => {
const res = await fetch(
`https://app.documenso.com/api/v1/templates//create-document`,
{
: ,
: {
: ,
: ,
},
: .({
: ,
: [{ : signer., : signer., : }],
}),
}
);
(!res.) ();
doc = res.();
(
,
{
: ,
: { : },
}
);
{ : signer., : doc. };
})
)
);
results.( {
(r. === ) r. ;
{ : signers[i]., : (r. ). };
});
}
Step 4: Async Processing with Background Jobs
import Bull from "bull";
const signingQueue = new Bull("documenso-signing", process.env.REDIS_URL!);
export async function queueSigningRequest(data: {
templateId: number;
signerEmail: string;
signerName: string;
}) {
const job = await signingQueue.add(data, {
attempts: 3,
backoff: { type: "exponential", delay: 5000 },
});
return job.id;
}
signingQueue.process(5, async (job) => {
const { templateId, signerEmail, signerName } = job.data;
return { status: "sent" };
});
signingQueue.on("completed", (job, result) => {
console.log(`Job ${job.id} completed: `);
});
signingQueue.(, {
.();
});
Step 5: Efficient Pagination
async function* iterateDocuments(client: Documenso, perPage = 50) {
let page = 1;
while (true) {
const { documents } = await client.documents.findV0({
page,
perPage,
orderByColumn: "createdAt",
orderByDirection: "desc",
});
for (const doc of documents) {
yield doc;
}
if (documents.length < perPage) break;
page++;
}
}
for await (const doc of iterateDocuments(client)) {
if (doc.status === "COMPLETED") {
await archiveDocument(doc.id);
}
}
Performance Targets
| Operation | Target | If Exceeded |
|---|
| Single document create | < 500ms | Check network latency |
| Template create + send | < 1s | Normal for template workflow |
| Batch of 100 documents | < 30s | Use concurrency 5-10 |
| Document list (page) | < 300ms | Add caching layer |
| Webhook processing | < 100ms | Process async, respond 200 immediately |
Error Handling
| Performance Issue | Cause | Solution |
|---|
| Slow responses | No connection reuse | Use singleton client pattern |
| Rate limit errors | Too many concurrent calls | Use p-queue with concurrency cap |
| Memory issues | Loading all documents | Use async generator pagination |
| Queue backlog | Slow processing | Increase worker concurrency |
Resources
Next Steps
For cost optimization, see documenso-cost-tuning.