How CloudMetrics Automated Dashboard Reports for 500+ Enterprise Clients
A customer success story about automating dashboard screenshot reports at scale. CloudMetrics reduced report generation from 4 hours to 20 minutes daily.
CloudMetrics is a business intelligence platform serving 500+ enterprise clients. Each client receives daily visual reports showing their key metrics, requiring thousands of dashboard screenshots every day. Manual capture was unsustainable—until they automated with Screenshotly.
The Challenge: Scale Without Compromise
The Numbers
- 500+ enterprise clients with custom dashboards
- 3-5 reports per client daily (morning, evening, weekly)
- 2,000+ screenshots needed daily
- 4 hours of manual work each day
- 2 dedicated staff for report generation
The Manual Process
Before automation, CloudMetrics' report workflow looked like this:
- Login to client dashboard with their credentials
- Navigate to each report view (5-10 per client)
- Wait for data to load (30-60 seconds per view)
- Capture screenshot with browser extension
- Crop and format in image editor
- Compile PDF report with all screenshots
- Email to client contacts
With 500+ clients requiring personalized reports, this consumed two full-time employees and still ran late some days.
Key Problems
- Inconsistent timing: Manual delays meant reports arrived at different times
- Quality variance: Different browsers, screen sizes, and capture methods
- Missing data: Screenshots sometimes captured before data fully loaded
- Staff burnout: Repetitive work with no creative value
- Scaling impossible: Adding new clients meant adding headcount
The Solution: Automated Screenshot Pipeline
CloudMetrics built an automated pipeline using Screenshotly's API, eliminating manual work entirely.
Architecture Overview
Scheduler (5 AM daily)
↓
Client Database
↓
Queue (per-client jobs)
↓
Screenshot Service
↓ (for each dashboard view)
Screenshotly API
↓
PDF Compilation
↓
Email Delivery
↓
Analytics Tracking
Implementation Details
1. Job Scheduler
// Runs at 5 AM daily
const clients = await db.getActiveClients();
for (const client of clients) {
await reportQueue.add({
clientId: client.id,
reportType: 'daily',
dashboards: client.dashboards,
recipients: client.reportRecipients,
});
}
2. Dashboard Capture
async function captureDashboard(dashboardUrl, sessionToken) {
const response = await fetch('https://api.screenshotly.app/screenshot', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: dashboardUrl,
device: 'desktop',
format: 'png',
viewport: { width: 1920, height: 1080 },
cookies: [
{
name: 'session',
value: sessionToken,
domain: 'app.cloudmetrics.io',
},
],
// Wait for charts to render
delay: 3000,
waitFor: '.chart-loaded',
aiRemoval: {
enabled: true,
types: ['notification', 'chat-widget'],
},
}),
});
if (!response.ok) {
throw new Error(`Capture failed: ${response.status}`);
}
return Buffer.from(await response.arrayBuffer());
}
3. Report Compilation
async function generateReport(client, screenshots) {
const pdf = new PDFDocument();
// Title page
pdf.addPage();
pdf.text(`Daily Metrics Report - ${client.name}`);
pdf.text(`Generated: ${new Date().toLocaleDateString()}`);
// Dashboard pages
for (const screenshot of screenshots) {
pdf.addPage();
pdf.text(screenshot.title);
pdf.image(screenshot.buffer, { width: 500 });
}
return pdf.toBuffer();
}
4. Delivery
async function deliverReport(client, pdfBuffer) {
await emailService.send({
to: client.reportRecipients,
subject: `Your Daily Metrics Report - ${formatDate(new Date())}`,
html: reportEmailTemplate(client),
attachments: [{
filename: `report-${formatDate(new Date())}.pdf`,
content: pdfBuffer,
}],
});
// Track delivery
await analytics.track('report_delivered', {
clientId: client.id,
reportType: 'daily',
});
}
Results: Transformation at Scale
Before & After
| Metric | Before | After | Improvement |
|---|---|---|---|
| Daily report time | 4 hours | 20 minutes | 91% faster |
| Staff required | 2 FTEs | 0 FTEs | 100% automated |
| Report consistency | Variable | 100% | Perfect quality |
| Delivery reliability | ~95% | 99.9% | Near-perfect |
| Cost per report | $2.50 | $0.15 | 94% savings |
Quantified Benefits
Time Savings:
- 4 hours/day × 260 working days = 1,040 hours/year saved
- Equivalent to 0.5 FTE freed for higher-value work
Cost Reduction:
- Previous: $150,000/year (2 FTEs + tools)
- Current: $9,000/year (API costs + infrastructure)
- Net savings: $141,000/year
Quality Improvements:
- Zero late reports (was 2-3/week)
- Consistent formatting (was variable)
- Always-current data (was sometimes stale)
Client Response
"Reports now arrive at exactly 6 AM every day. The consistency gives our team confidence in the data." — Director of Operations, Fortune 500 Client
"We used to wait until 10 AM some days. Now we start our morning standups with fresh metrics." — VP Analytics, SaaS Client
Technical Highlights
Authentication at Scale
CloudMetrics manages 500+ client credentials securely:
// Encrypted credential storage
const credentials = await vault.getClientCredentials(clientId);
// Generate session token
const session = await authService.login(
credentials.username,
credentials.password
);
// Use session for screenshot
const screenshot = await captureDashboard(
dashboardUrl,
session.token
);
// Session expires after use
await authService.logout(session.token);
Parallel Processing
Capture multiple dashboards simultaneously:
const pLimit = require('p-limit');
const limit = pLimit(10); // 10 concurrent captures
async function captureAllDashboards(client) {
const promises = client.dashboards.map(dashboard =>
limit(() => captureDashboard(dashboard.url, client.sessionToken))
);
return Promise.allSettled(promises);
}
Error Recovery
Robust retry logic for reliability:
async function captureWithRetry(url, options, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await captureDashboard(url, options);
} catch (error) {
if (attempt === maxRetries) throw error;
// Exponential backoff
await sleep(Math.pow(2, attempt) * 1000);
}
}
}
Monitoring Dashboard
CloudMetrics built an internal dashboard to monitor report generation:
- Real-time status: Which reports are generating, complete, or failed
- Timing analytics: How long each client's reports take
- Error tracking: Immediate alerts for failures
- Usage metrics: API calls, costs, trends
Lessons Learned
1. Wait for Data
Initial captures sometimes missed data because charts hadn't loaded:
// Solution: Wait for data indicators
waitFor: '.chart-loaded',
delay: 3000,
2. Handle Dynamic Content
Some dashboards have animations that affect screenshots:
// Inject CSS to disable animations
injectStyles: `
* {
animation: none !important;
transition: none !important;
}
`
3. Optimize Image Size
Full-resolution dashboards created large PDFs:
// Resize for PDF while maintaining clarity
viewport: { width: 1920, height: 1080 },
format: 'png',
// Resize in processing step if needed
4. Plan for Failures
Not every capture succeeds. CloudMetrics implemented:
- Retry logic: 3 attempts with backoff
- Fallback images: "Report unavailable" placeholder
- Alerting: Immediate notification for persistent failures
- Partial delivery: Send available sections, note missing ones
Future Plans
CloudMetrics is expanding automation:
- White-label reports: Custom branding per client
- Interactive PDFs: Clickable charts linking to live dashboards
- Scheduled comparisons: Week-over-week visual diffs
- Slack/Teams delivery: Reports posted to channels
- Executive summaries: AI-generated insights from screenshots
Getting Started
If you're facing similar challenges, here's the path CloudMetrics recommends:
- Audit current process: Count time and resources spent
- Start small: Automate one client first
- Iterate quickly: Refine based on output quality
- Scale gradually: Add clients as you build confidence
- Monitor everything: Track timing, errors, and costs
Conclusion
Automating dashboard reports transformed CloudMetrics' operations:
- 91% time reduction in daily report generation
- 94% cost savings compared to manual process
- 99.9% delivery reliability vs ~95% before
- Zero headcount required for report generation
The investment in automation paid for itself within the first month. Now, the team focuses on building better analytics products instead of manually capturing screenshots.
Ready to automate your reports?
Get your free API key → - 100 free screenshots to get started.
Learn more about automated report generation →
About the Author

Asad Ali
Full-Stack Developer and Founder of ZTabs with 8+ years of experience building scalable web applications and APIs. Specializes in performance optimization, SaaS development, and modern web technologies.
Credentials: Founder & CEO at ZTabs, Full-Stack Developer, Expert in Next.js, React, Node.js, and API optimization
Ready to capture your first screenshot?
Get started with 100 free screenshots. No credit card required.