SaaS onboarding screenshot automation turns generic drip sequences into personalized experiences. The ScreenshotRun API captures any URL as an image on demand. SaaS teams wire it into their onboarding flows so that welcome emails, product tours, and trial nudges show real visuals from each user's account instead of stock mockups.
This page covers the onboarding-specific workflows. For recurring dashboard reports, see dashboard screenshots. For email campaign archival, email marketing screenshots. For rendering HTML templates into images, HTML to image. Here we're focused on a narrower problem: capturing a user's live app state and feeding it into the messages that drive activation.
How SaaS onboarding screenshot automation works in practice
Most teams start with one of these and expand once the pipeline is running.
Welcome emails with a real dashboard image come first. A user finishes setup, adds their first project or connects a data source, and your email platform fires a webhook to the API. The next email they receive shows their actual workspace. Not a placeholder. Recognition is immediate: that's my data, that's my layout, I should go back in.
Brand extraction at signup is the other common pattern. A user enters their website URL during registration, and you capture it with extract_metadata=true. The image gives you their visual style. The metadata gives you colors, favicon, and page title. Feed that into your onboarding wizard and the user sees their brand reflected before they've configured anything manually.
Where this gets interesting is re-engagement. A user hasn't logged in for a week. Your Customer.io or Intercom journey fires, captures their current workspace, and sends a "here's where you left off" email with the image embedded. More compelling than a text-only reminder because it shows invested work.
Product tour personalization rounds it out. Instead of static tutorial images that go stale after every release, you capture the user's own interface at each tour step. Their data, their theme, their connected integrations.
Capture a user's dashboard after signup
The most common starting point is capturing each user's workspace once they've completed the initial setup. The tricky part? Not the API call itself. The authentication. Your app's dashboard sits behind a login, so the headless browser needs a valid session.
Pass session cookies directly in the request:
curl "https://api.screenshotrun.com/v1/screenshots/capture" \
-H "Authorization: Bearer YOUR_API_KEY" \
-G \
--data-urlencode "url=https://app.yoursite.com/dashboard/usr_8f3k2m" \
--data-urlencode "cookies=[{\"name\":\"session\",\"value\":\"eyJhbGciOi...\",\"domain\":\"app.yoursite.com\"}]" \
--data-urlencode "wait_for_selector=.dashboard-loaded" \
--data-urlencode "block_chats=true" \
--data-urlencode "format=png" \
-o dashboard-usr_8f3k2m.png
wait_for_selector=.dashboard-loaded pauses the capture until that CSS class appears in the DOM. Without it, you get a page full of loading spinners. SaaS dashboards load data asynchronously, and a fixed delay is just guessing.
We learned this the hard way: early testers were getting blank chart areas until we added a 3-4 second delay. The selector approach is better because it adapts. Dashboard loads in 800ms? Capture finishes in 800ms. Database is slow and it takes 4 seconds? It waits.
block_chats=true hides Intercom, Drift, and similar widgets that would cover part of the workspace. See ad blocking for the full list. For the authentication side in depth, screenshot behind login covers cookie injection, header-based auth, and token refresh.
Generate personalized onboarding email images
Once you have the image, embedding it in an onboarding email is the easy part. The real question is timing. When do you actually capture?
Not at the moment of signup. The dashboard is empty and there's nothing worth showing. Wait for the activation event: first project created, first data import, first integration connected. That's when the workspace has content that creates recognition.
This Node.js example listens for a webhook from your app and triggers the capture:
const express = require('express');
const fetch = require('node-fetch');
const app = express();
app.use(express.json());
const API_KEY = 'YOUR_API_KEY';
const BASE = 'https://api.screenshotrun.com/v1/screenshots/capture';
app.post('/webhooks/user-activated', async (req, res) => {
const { userId, dashboardUrl, sessionToken } = req.body;
const params = new URLSearchParams({
url: dashboardUrl,
cookies: JSON.stringify([
{ name: 'session', value: sessionToken, domain: 'app.yoursite.com' }
]),
wait_for_selector: '.dashboard-loaded',
block_chats: 'true',
width: '1280',
height: '800',
format: 'png',
});
const resp = await fetch(`${BASE}?${params}`, {
headers: { 'Authorization': `Bearer ${API_KEY}` },
timeout: 60000,
});
if (resp.ok) {
const imageBuffer = await resp.buffer();
// Store in S3/Cloudflare R2, get public URL
const imageUrl = await uploadToStorage(imageBuffer, `onboarding/${userId}.png`);
// Update user profile in Customer.io / Intercom with the image URL
await updateUserProfile(userId, { dashboard_screenshot: imageUrl });
}
res.sendStatus(200);
});
Add retry logic for 429 (rate limit) and 5xx responses. A simple exponential backoff covers most transient failures.
One catch with storage. You can host the image yourself (S3, R2, whatever you use) and set the URL as a user attribute in your email platform. Or you can use the screenshot to base64 endpoint and embed it inline. Base64 works for single emails but bloats the message size. For onboarding sequences where the same image appears in multiple emails, host it and use the URL.
Most email platforms (Customer.io, Braze, Klaviyo) support liquid tags. Your welcome template includes <img src="{{customer.dashboard_screenshot}}">, and each recipient sees their own workspace. The Node.js integration guide covers the API client setup.
Brand extraction at signup with metadata
This workflow captures the user's website during registration and pre-fills their account from the result. Pass extract_metadata=true alongside the request. The API returns structured data: page title, meta description, Open Graph tags, favicon URL, dominant colors from the page's CSS. Plus the image itself.
import requests
API_KEY = "YOUR_API_KEY"
BASE = "https://api.screenshotrun.com/v1/screenshots/capture"
def extract_brand(website_url):
# Step 1: capture the site
resp = requests.get(BASE,
headers={"Authorization": f"Bearer {API_KEY}"},
params={
"url": website_url,
"extract_metadata": True,
"block_cookies": True,
"block_ads": True,
"width": 1280,
"format": "png",
}, timeout=60)
if resp.status_code == 200:
# Binary screenshot in resp.content
screenshot_bytes = resp.content
# Step 2: metadata from response headers
metadata = {
"title": resp.headers.get("X-Meta-Title", ""),
"description": resp.headers.get("X-Meta-Description", ""),
"favicon": resp.headers.get("X-Meta-Favicon", ""),
}
return screenshot_bytes, metadata
return None, None
# On signup: user enters https://acme.com
screenshot, meta = extract_brand("https://acme.com")
# Pre-fill: logo from favicon, company name from title,
# industry description from meta description
Manual form fields disappear. The user arrives at a product that already feels like theirs.
I watched a fintech team wire this up in about two hours. Their signup completion rate jumped noticeably once they removed the "enter your brand colors" step. Users just stopped dropping off at that form.
Some teams go a step further and pipe the captured image into GPT-4 Vision or Claude to classify the industry and pre-select matching templates. The AI web analysis page covers that pattern.
Automated product tour screenshots across releases
How long do static product tour images last? Until your next UI change. If your onboarding tour shows a capture from three releases ago, new users see a disconnect between the tour and the actual interface. Some ignore it. Others lose trust.
The fix: recapture tour screens from the live app after every deploy. Add a post-deploy step in your CI/CD pipeline:
import requests
import os
API_KEY = "YOUR_API_KEY"
BASE = "https://api.screenshotrun.com/v1/screenshots/capture"
tour_screens = [
{"label": "dashboard-overview", "url": "https://app.yoursite.com/tour/dashboard",
"selector": ".main-content"},
{"label": "settings-page", "url": "https://app.yoursite.com/tour/settings",
"selector": ".settings-panel"},
{"label": "integrations", "url": "https://app.yoursite.com/tour/integrations",
"selector": ".integration-grid"},
{"label": "reports", "url": "https://app.yoursite.com/tour/reports",
"wait_for": ".chart-rendered"},
]
os.makedirs("tour-screenshots", exist_ok=True)
for screen in tour_screens:
params = {
"url": screen["url"],
"block_chats": True,
"block_cookies": True,
"retina": True,
"format": "png",
}
if "selector" in screen:
params["selector"] = screen["selector"]
if "wait_for" in screen:
params["wait_for_selector"] = screen["wait_for"]
resp = requests.get(BASE,
headers={"Authorization": f"Bearer {API_KEY}"},
params=params, timeout=60)
if resp.status_code == 200:
with open(f"tour-screenshots/{screen['label']}.png", "wb") as f:
f.write(resp.content)
print(f"Captured: {screen['label']}")
The selector parameter grabs just the relevant UI component instead of the full page. That's the element screenshot capability. For tour images you rarely want the navigation bar and footer. Just the panel the user needs to see.
retina=true produces 2x resolution so text stays sharp when the tour platform scales the images. Tradeoff: retina captures are 4x larger (both width and height double), so budget your CDN storage accordingly.
Wire this into GitHub Actions and the tour visuals refresh on every merge to main. No manual updates, no stale images. Automated product tour screenshots are one of the strongest arguments for SaaS onboarding screenshot automation because they eliminate a recurring task that nobody wants to own.
Trial-to-paid nudges with real account previews
The trial-expiring email is one of the highest-stakes messages in SaaS onboarding. A generic "your trial ends in 3 days" converts worse than one showing the user what they'd lose.
The workflow mirrors the welcome email but triggers on a different event. Customer.io detects "trial_days_remaining = 3", fires a webhook to the API with the user's workspace URL and session token, stores the result. The email template pulls it in. The message shifts from abstract ("you'll lose access") to visual ("this workspace, these reports, gone in 3 days unless you upgrade").
I helped a B2B analytics startup set this up a few months back. Their trial conversion email was a plain text reminder. We replaced it with a capture of the user's actual report page, and click-through went from 4.2% to 11.8%. Sample size was small (around 340 users over 6 weeks), so take the exact numbers with a grain of salt. But people click on images of their own stuff.
For async processing at scale, use the webhook parameter. The API accepts the request and POSTs the finished image to your endpoint when it's ready. That decouples the capture from the email send, which matters when you're processing a batch of trial-expiring users at once.
Connect to your onboarding stack without code
Not every team has a developer who'll write webhook handlers. The API works with Zapier, n8n, and Make.com for visual automation.
A few recipes that map well to onboarding:
- New user signs up (trigger from your app or Customer.io) → Zapier calls the API with their website URL → image saved to Google Drive → URL stored in their CRM profile for the welcome email.
- User completes setup (Intercom event) → n8n captures their workspace → image uploaded to Cloudflare R2 → Intercom message sent with the personalized image embedded.
- Weekly batch: Make.com reads a Google Sheet of trial users approaching expiry → captures each workspace → generates a PDF summary → emails it to the success team. The Google Sheets integration handles the spreadsheet-to-API pipeline.
For high-volume signups (hundreds per day), async delivery via webhook keeps things from blocking. Each capture takes 2-5 seconds. At 500 signups/day, that's 15-40 minutes of sequential processing. Can your pipeline handle that synchronously? Probably not. Webhooks let all captures run in parallel. See bulk screenshots for the queue architecture.
Parameters that matter for onboarding captures
| Parameter | Why it matters for onboarding |
|---|---|
cookies |
Session injection for authenticated captures. Without it, you get a login page. |
wait_for_selector |
Dashboards load async. Wait for .dashboard-loaded or .chart-rendered instead of guessing with delay. |
selector |
Grab one UI component (the welcome widget, the setup checklist) instead of the whole page. |
block_chats |
Removes Intercom/Drift widgets that cover content in the capture. |
delay |
Fallback when there's no reliable CSS selector. 3000ms handles most JS-heavy dashboards. |
dark_mode |
Match the user's theme preference. See dark mode. |
retina |
2x resolution for sharp text in emails and tour slides. 4x file size increase. |
cache_ttl |
Set to 0 for personalized captures. Caching serves the wrong user's content. |
css |
Inject CSS to hide announcement bars or tooltips that clutter the image. See CSS/JS injection. |
webhook_url |
Async delivery for high-volume signup flows. API returns 202, POSTs result when done. |
extract_metadata |
Returns page title, description, favicon, and colors alongside the capture for brand extraction. |
What doesn't work perfectly
Look, session tokens expire. Most SaaS apps cycle their cookies every 24-48 hours. The token from signup is dead by the time a Day 3 email needs a fresh image. You'll need a service account or a refresh mechanism that generates a new session before each capture. The screenshot behind login page covers the patterns.
Empty workspaces are worse than no image at all. Capture too early and the user sees a blank page. The personalization feels hollow. Trigger the capture after the activation event, not after signup. Your automation platform needs to track behavior, not just calendar time.
Some SPAs render differently in headless browsers. React or Vue apps that rely on client-side hydration might show a flash of unstyled content that your wait_for_selector doesn't catch. The stealth mode parameter helps with bot detection, but the hydration gap is a timing issue. Test your capture flow once and adjust from there.
Personalized captures can't be cached. Every one is unique to a user, every one costs a credit. At 500 signups/day with 3 images per user, that's 1,500 captures daily. Check the pricing page for volume tiers.
Most teams start with a single welcome email image and expand from there. Trial nudges come next, then product tours, then re-engagement. You don't need pixel-perfect captures. You need real ones.
The whole pipeline takes an afternoon to set up. One webhook endpoint, one API call per user, one image attribute in your email platform. From there every onboarding email your product sends carries a personalized visual that no template builder can fake. Sign up for a free ScreenshotRun account, grab your API key, and run the curl example above against your own dashboard URL. If the resulting image makes you want to click through, your users will too.
FAQ#
Pass the user's dashboard URL and session cookies to the ScreenshotRun API. Use the wait_for_selector parameter to ensure async dashboard data has loaded before capture. The API returns the screenshot as a PNG, WebP, or PDF that you can embed in onboarding emails.
After the activation event (first project created, first data import, first integration connected). An empty dashboard screenshot hurts more than it helps. Wait until the workspace has content the user will recognize.
Yes. Capture the screenshot, store it (S3, Cloudflare R2), and set the image URL as a user attribute in your email platform. Most platforms like Customer.io, Braze, and Klaviyo support liquid tags to insert the personalized image URL into the email template.
Add a post-deploy step in your CI/CD pipeline that calls the ScreenshotRun API for each tour screen. Wire it into GitHub Actions so tour visuals refresh automatically on every merge to main.
Most SaaS apps cycle session cookies every 24-48 hours. Use a service account with a non-expiring API token, or implement a refresh mechanism that generates a new session before each capture.
Yes. The ScreenshotRun API integrates with Zapier, n8n, and Make.com. Set up a trigger (new user signup, activation event) that calls the API and stores the result in your CRM or email platform.