SSS DocGen — Cost & Cost-Efficiency
Where the money actually goes at volume, and the levers that reduce it. Worked at 50,000 invoices/month. Prices are approximate (West Europe, ~2026) — order-of-magnitude, not a quote.
TL;DR
- Compute is essentially free. Scale-to-zero ACA (1 vCPU / 2 GiB) plus the monthly free grants put Azure run-cost at ~$150–300/yr all-in — dominated by ACR + Log Analytics, not the container. Marginal compute is ~$0.0004/invoice.
- The real cost is storing the generated PDF in Dataverse. Every document is written to Dataverse File capacity (~$2/GB/month), which compounds as documents accumulate. At 50k/mo that is the line item to manage — potentially 10× the Azure bill and growing.
Cost breakdown at 50,000 invoices/month
Assumptions: ~150 KB per PDF; ~6 s active compute per invoice.
Azure infrastructure — negligible
| Component | Basis | Monthly |
|---|---|---|
| ACA compute – vCPU | 300k vCPU-s − 180k free grant → 120k × $0.000024 | ~$3 |
| ACA compute – memory | 600k GiB-s − 360k free grant → 240k × $0.000003 | ~$1 |
| ACA requests | 50k ≪ 2M free/month | $0 |
| Container Apps environment | Consumption plan, no base fee | $0 |
| Log Analytics | ~1–3 GB ingest after 5 GB free | $0–8 |
| Container Registry (ACR) | Basic (fixed) | ~$5 |
| Key Vault + egress | secret cached; ~7.5 GB egress | <$2 |
| Azure total | ~$10–25/mo (~$150–300/yr) |
Even at 12 s/invoice, compute only reaches ~$10–15/mo. Not worth optimizing.
Dataverse — the driver
Historically DeliveryService stored the PDF twice per invoice: as annotation.documentbody
and as activitymimeattachment.body on a draft email — both in File capacity.
| Item | Basis | Cost |
|---|---|---|
| PDF storage growth (1 copy) | 50k × 150 KB ≈ 7.5 GB/month | ~$2/GB/mo, compounding |
| Second copy (draft-email attachment) | another 7.5 GB/month | doubles the above |
| Year-1 accrual (1 copy, retained) | ~90 GB by month 12 | ~$140/mo by month 12; ~$900 spent in year 1 |
| Power Platform requests | ~8 Dataverse calls/invoice ≈ ~400k/month | verify app-user entitlement |
Cost-efficiency levers (ranked by impact)
1. Store the PDF in Azure Blob instead of Dataverse (~99% on storage)
Dataverse File ≈ $2/GB/mo; Azure Blob ≈ $0.02 (hot) / $0.01 (cool) / $0.001
(archive). Render → upload to Blob (lifecycle rule tiers to Cool/Archive and auto-deletes after
the retention window) → write the blob URL + metadata onto the record → return a short-lived SAS
link (the API already returns fileBase64, so callers don't depend on Dataverse storage). ~90 GB/yr
costs ~$1–2/mo on Blob vs ~$140/mo on Dataverse.
- Trade-off: annotations show natively in the record timeline; a Blob link needs a URL field or a small custom control. Retrieval needs a SAS token or a tiny download endpoint.
- For legal/immutable invoices: use a Blob immutability (WORM) policy — still far cheaper than Dataverse.
2. Don't store a second copy on a draft email — implemented
DeliveryService now creates the draft email (and its duplicate attachment) only when
Delivery:CreateDraftEmail = true, which defaults to false (DeliveryOptions). By default a
document is stored once (the annotation), halving storage growth immediately. Set the flag per
environment where a drafted email is genuinely needed.
- Note: a Dataverse email attachment is inherently its own File-capacity copy — there is no supported "attach the existing note" that avoids the second blob. Hence the choice is draft email on (2 copies) vs off (1 copy), exposed as config.
3. Generate lazily — only what's consumed
Today all 50k render + store eagerly. If only a fraction are ever downloaded/emailed, render on demand and cache the result: storage and compute scale with the access rate, not the invoice count. Best when an up-front immutable copy of every document isn't required.
4. Regenerate on demand, store nothing ($0 storage — caveat)
PDFs are deterministic from Dataverse data, so you can keep zero copies and re-render on request. Caveat: if source data changes, a regenerated invoice won't match the original — fine for statements/previews, but for retained invoices prefer lever 1 (immutable Blob).
What NOT to change
- Compute / ACA: already optimal via scale-to-zero + free grants (~$3–10/mo). Switching to Functions or a lighter PDF engine (QuestPDF/wkhtmltopdf) saves cents and would abandon the Handlebars-HTML model. Leave it.
- The engine pipeline (FetchXML → enrich → Handlebars → Chromium) is fine; only the delivery tail was expensive.
Recommended target
Blob (Cool + lifecycle) for the retained copy + a link on the record; draft email off by default (done); annotations reserved only where the timeline UI is required (and then a link note, not the bytes). This takes the dominant cost from ~$900+/yr and compounding → ~$10–20/yr, leaving Azure compute (~$150–300/yr) as the only real run cost.
Operational note (not cost)
maxReplicas: 2 × concurrency 20 = ~40 in-flight. A month-end batch of thousands of invoices at
once will queue/throttle; raise maxReplicas for batch windows (same total vCPU-seconds, just more
parallelism).