Clearform
Weekly customer reports delivered without a cron-job archaeology team.
Analytics SaaS · Series A · 22 engineers
The build
Clearform delivers a Monday-morning PDF report to 5,200 B2B customers. Previously, a 3-node Puppeteer cluster generated them. Now it's a Vercel Cron → SnapPDF pipeline with 99.98% delivery reliability.
Stack
- · Next.js
- · PostgreSQL
- · Vercel Cron
- · Chart.js
- · Resend
- · SnapPDF
Architecture
Ops used: /from-images → /merge → /add-page-numbers → /watermark
How they use SnapPDF
Clearform tracks product-engagement metrics for B2B SaaS companies. Every Monday at 06:00 UTC, each customer receives a PDF summarizing the previous week — usage by cohort, retention curves, feature-adoption deltas, a custom executive summary generated by a prompt-chained LLM pipeline.
For two years, the report generator ran on a 3-node Puppeteer cluster behind a queue. Every Sunday night, the ops team watched dashboards. Memory leaks in Chromium, PDF-size bloat from large tables, and intermittent crashes meant ~6% of reports missed the Monday window. Customers noticed; three complaints a week correlated tightly with churn risk.
The migration to SnapPDF took one sprint. Chart.js already rendered to canvas in the browser; they ported that to a Node + headless-canvas pipeline that emits PNGs per chart. The PNGs flow into /from-images to become PDF pages, /merge stitches with a pre-built cover and appendix, /add-page-numbers adds "Page N of M" with a roman-numeral prefix for the exec summary, and /watermark stamps the account name discretely at the bottom.
Measured results: on-time delivery moved from 94% to 99.98%; infrastructure cost dropped by $290/month (EC2 + S3 saved); engineering time allocated to the report pipeline fell from 0.3 FTE to 0.02 FTE — essentially, nobody owns it anymore except a Slack alert that has never fired. The one-time migration cost: 12 engineering days.
The pattern generalizes: if you render charts to bitmaps anywhere in your stack, the from-images → merge pipeline is the lowest-friction path to programmatic PDF output. Clearform now also offers "one-off report" exports to customers on-demand, using the same pipeline triggered by a button click — a feature their enterprise customers had been asking for for 18 months.
Outcomes
Integration pattern
A simplified excerpt showing the core SnapPDF calls.
// apps/reports-worker/src/generate.ts
import { SnapPDF } from '@snappdf/sdk';
const snap = new SnapPDF({ apiKey: process.env.SNAPPDF_KEY! });
export async function generateWeeklyReport(customerId: string) {
const customer = await db.customers.findById(customerId);
const weekData = await analytics.getWeek(customerId);
// 1. render charts server-side to PNG
const chartPngs = await Promise.all(
weekData.charts.map((c) => renderChartToPng(c)),
);
// 2. PNGs → PDF pages
const { pdf: chartPdfs } = await snap.pdf.fromImages({
images: chartPngs.map((png) => ({ data: png, mime: 'image/png' })),
pageSize: 'a4',
fit: 'contain',
margin: 48,
});
// 3. merge with cover + exec summary
const { pdf: full } = await snap.pdf.merge({
files: [coverFor(customer), execSummaryFor(weekData), chartPdfs, APPENDIX_URL],
preserveBookmarks: true,
});
// 4. pagination
const { pdf: paginated } = await snap.pdf.addPageNumbers({
file: full,
template: 'Page {n} of {total}',
position: 'bottom-center',
skipFirst: 2,
});
// 5. account-name watermark
const { pdf: stamped } = await snap.pdf.watermark({
file: paginated,
kind: 'text',
text: customer.displayName.toUpperCase(),
position: 'bottom-center',
opacity: 0.08,
color: '#64748b',
});
return stamped;
}Start building like this
Free tier gives you 100 ops/month — enough to prototype any of the flows on this page. No card required.