One engine, different responsibilities
Invoices, proposals and contracts share a technical need: turn structured data into a document. Each family still has distinct rules, retention periods and approval requirements.
The goal is not one universal template. It is a shared generation infrastructure with separate models and data contracts.
Build a common pipeline
The pipeline can own cross-cutting work:
- authenticate the calling application;
- check access to the template;
- validate the payload;
- create an idempotent job;
- render the document;
- store output privately;
- publish state and webhooks;
- apply retention.
Rules specific to an invoice or contract stay upstream in the business service.
Invoices: accuracy and repeatability
Invoices require stable identifiers, amounts calculated by the source system and context-appropriate legal information. The template presents those values; it should not become the accounting engine.
Test display rounding, currencies, discounts, multiple tax rates and tables that span pages.
Proposals: modular content and speed
A proposal mixes editorial content with priced lines. Reusable blocks let teams compose cover, context, timeline, services and terms without duplicating the whole template.
If you track document views, use a dedicated link and inform recipients as required for your context.
Contracts: control and accountability
Contracts need stricter review. The published version must be identifiable, variables should be constrained and sensitive clauses reviewed by the appropriate person.
Document generation replaces neither legal advice nor electronic signature. It produces a document from an approved model.
Avoid duplicate integrations
Expose one interface that varies mainly by template version and payload type:
await documents.generate({
templateVersionId: selectedVersion,
data: businessPayload,
idempotencyKey: businessReference
});
Teams can add a new document family without inventing new authentication, tracking and storage mechanisms.
Architecture checklist
- One data contract per document family.
- Business calculations stay in source services.
- A shared job pipeline handles rendering and observability.
- API keys are scoped by use.
- Each family has a retention policy.
- Published models are versioned.
- Events can be replayed without duplicates.
Frequently asked questions
Do we need one template per customer?
Not always. Start with a shared template and brand variants. Split when a customer genuinely requires a different structure or obligations.
Where should conditional business rules live?
Simple presentation conditions can live in the template. Financial, legal or contractual decisions belong in the business system that can test and audit them.