Screenshot Email Marketing Campaigns via API

Capture full-page screenshots of sent email campaigns from Mailchimp, Klaviyo, HubSpot, and more. Automate campaign archives, client reports, and A/B test docs via API.

Vitalii Holben Updated

Every sent campaign has a web version — a browser-renderable URL that Mailchimp, Klaviyo, HubSpot, and most other ESPs generate automatically. The ScreenshotRun API captures those URLs as full-page PNG, JPEG, or PDF archives. No manual screenshots. No browser tabs to open. Just an HTTP call after each send.

This page covers the URL-based workflow for documenting live sent campaigns. If you want to render HTML email markup directly (for pre-send rendering tests), that's the email-to-image feature. For cross-client compatibility previews before you hit send, see email previews. The use case here is different: you've already sent the campaign, and you need a pixel-accurate record of what went out.

What the API captures

Most ESPs produce a "view in browser" link for every campaign — something like https://mailchi.mp/example/summer-sale-2026 or https://campaign.r20.constantcontact.com/render?.... These are real web pages. They load fonts, images, and layout exactly as a subscriber would see them in a browser. That's what you pass to the API.

The renderer opens the URL in a headless Chrome instance, waits for images and web fonts to fully load, and returns a screenshot. A 40-section newsletter comes back as one tall PNG. Your Black Friday campaign from last November, preserved exactly as it appeared. You don't need to re-upload HTML or track down template files later.

And because it's a URL call, you can automate the whole thing: capture after every send, or batch-process a month of campaigns in one script run.

What you get is essentially a read-only screenshot of a live URL — the same thing a browser would show, minus the need to open one. The API doesn't interact with your ESP, doesn't need access to your account, and doesn't touch your subscriber list. It just renders whatever is publicly accessible at that URL.

Full-page capture for long newsletters

Email campaigns can run 8,000–15,000 pixels tall. Default viewport capture cuts off at the browser window height. You want full_page=true here — the API scrolls the entire document and stitches the result into a single image.

But there's a catch with image-heavy campaigns: product images often lazy-load on scroll. Without a delay, the screenshot catches some of them mid-load (or blank entirely). Set delay=2000 to let the page settle after scroll. For campaigns with animated banners or heavy third-party content, bump it to 3000ms. How do you know which one you need? Check the first capture — if product images are missing or gray, add more delay.

curl "https://api.screenshotrun.com/v1/screenshots/capture" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -G \
  --data-urlencode "url=https://mailchi.mp/example/summer-sale-2026" \
  --data-urlencode "full_page=true" \
  --data-urlencode "delay=2000" \
  --data-urlencode "width=700" \
  --data-urlencode "format=png" \
  -o summer-sale-2026.png

Width 700px matches the standard email container (most ESPs default to 600–700px). You'll get a screenshot that looks like the campaign in a desktop inbox, not stretched to 1920px with odd white space on both sides. For mobile versions, set width=375 or use device=mobile.

Automating campaign archives

The manual approach: someone opens a browser, scrolls through each sent campaign, takes a screenshot, renames the file, drops it in a shared folder. This works for 5 campaigns a month. It doesn't work for 50 — and it definitely doesn't work when you need to back-fill three months of archives before a client audit.

Below is a Python script that reads a CSV of campaign slugs and web version URLs, then saves each one to a local archive folder. Replace the CSV with an export from your ESP's API, and this becomes a fully automated post-send step.

import csv
import os
import requests

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.screenshotrun.com/v1/screenshots/capture"
os.makedirs("campaign-archive", exist_ok=True)

with open("campaigns.csv") as f:
    for row in csv.DictReader(f):
        resp = requests.get(
            BASE_URL,
            headers={"Authorization": f"Bearer {API_KEY}"},
            params={
                "url": row["web_version_url"],
                "full_page": True,
                "delay": 2000,
                "width": 700,
                "format": "png",
                "block_ads": True,
            },
            timeout=60,
        )
        slug = row["slug"]
        if resp.status_code == 200:
            with open(f"campaign-archive/{slug}.png", "wb") as out:
                out.write(resp.content)
            print(f"Archived: {slug}")
        else:
            print(f"Error on {slug}: {resp.text}")

block_ads=true strips retargeting scripts and third-party banners that ESP web version pages occasionally load. It won't touch your campaign content (just the surrounding noise). See ad blocking for what it actually removes.

Agency client reporting

Agencies sending 30+ campaigns a month across multiple client accounts face the same recurring problem: the client wants to see what went out. A live link to the ESP dashboard requires login. Screenshots embedded in a monthly report don't.

The usual workflow is: export the month's campaign list (slugs + web version URLs) from your ESP, batch-capture all of them, then embed the images into a PDF report or a client-facing Notion page. For bulk capture, running 10 concurrent requests instead of sequential ones cuts a 30-campaign batch from 90 seconds to under 15. That's the difference between a script that runs in the background during lunch and one that blocks your team's workflow.

Want PDFs instead of PNGs? Swap format=pdf and set full_page=true. One PDF per campaign, ready to attach to a monthly email. You can also stitch multiple campaign PDFs into a single document using PyPDF2 or pdf-lib, depending on your stack.

One thing agencies find useful: capturing at consistent dimensions across all clients makes visual comparison across accounts cleaner. Set a standard width (700px for newsletters, 600px for transactional) in your batch script and use it everywhere. When a client asks "can we see last quarter's campaigns side by side?" — you'll have a uniform archive ready.

A/B test documentation

Most ESPs test subject lines, not email bodies. But when you do run layout tests — different hero images, different CTA treatments, different content block sequences — you need a visual record of what each variant looked like.

Why does this matter? Because once the winning variant is promoted and the loser expires, the web version URL disappears. Thirty to sixty days after the test closes (depending on your ESP's archiving policy), both are gone. The only thing left is whatever you saved.

Both variant URLs exist in your ESP for that window. Capture them as soon as the test closes and the winner is selected. Label them clearly (test-47-variant-a.png, test-47-variant-b.png) and attach them to your test retrospective. When you're briefing the next campaign, that visual record is actually useful — it's the thing that explains the decision, not just the click rate.

Which ESPs generate public web version URLs

ESP Web version URL pattern Auth required?
Mailchimp mailchi.mp/... No — fully public
Klaviyo Campaign detail → "View in Browser" No — public after send
Constant Contact campaign.r20.constantcontact.com/render?... No — public, long URL
ActiveCampaign Campaign → "View in Browser" link No — public after send
Brevo (ex-Sendinblue) Available in campaign report No
HubSpot Email preview link in campaign editor Sometimes — token in URL for some setups

HubSpot is the tricky one. Some enterprise configurations require a HubSpot-issued token in the URL for the web version to load. But standard HubSpot sends generate fully public links — same as the others. If you're not sure whether your HubSpot setup uses token-protected links, open an incognito window and try loading the web version URL without being logged in. If it loads, you're good.

Salesforce Marketing Cloud and Marketo also generate web version URLs, but both tend to use more complex authentication setups. Possible to capture, but expect to use the cookies parameter to pass session tokens.

Triggering captures automatically via webhook

If your ESP supports outbound webhooks on campaign send events, you can skip the manual CSV export entirely. The webhook fires when a campaign is sent; your handler captures the web version immediately. Here's a lightweight Node.js handler:

const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());

app.post('/campaign-sent', async (req, res) => {
  const { campaignId, webVersionUrl } = req.body;

  // Wait 8 seconds — some ESPs take a moment to make the web version live
  await new Promise(r => setTimeout(r, 8000));

  await axios.get('https://api.screenshotrun.com/v1/screenshots/capture', {
    headers: { Authorization: `Bearer ${process.env.SCREENSHOTRUN_KEY}` },
    params: {
      url: webVersionUrl,
      full_page: true,
      delay: 2000,
      width: 700,
      format: 'png',
      // Deliver the finished screenshot to your archive endpoint
      webhook_url: `https://your-app.com/archive?id=${campaignId}`,
    },
  });

  res.sendStatus(200);
});

app.listen(3000);

The 8-second wait exists because ESP web versions sometimes take a few seconds to become publicly available after the send event fires. Without it, you occasionally capture a 404 page. Not ideal. You could also build a simple retry — fetch the URL with a HEAD request, and only trigger the screenshot API call once it returns 200.

For large send volumes, the ScreenshotRun webhook parameter handles async delivery. The API accepts your request immediately and POSTs the finished screenshot to your endpoint when rendering completes. Your handler responds with 200 right away, and the screenshot arrives a few seconds later when it's ready.

Compliance archiving

Financial services firms, healthcare marketers, and some regulated B2B sectors need documented records of every marketing communication sent to customers. Email is no exception. FINRA, SEC, and CAN-SPAM all have retention requirements that a visual archive supports.

Adding certified=true to any capture request returns a SHA-256 hash and ISO 8601 timestamp alongside the image. That gives you a verifiable record that the screenshot was captured at a specific point in time, not reconstructed later. Does it replace a dedicated compliance archiving platform? No — tools like Smarsh or Global Relay handle supervision and eDiscovery at a level the API doesn't. But for teams that need a cost-effective audit trail without $50K/year enterprise software, it's a reasonable starting point. The certified screenshot page covers what the certification object contains. The social media compliance page covers the broader regulatory archiving workflow.

Limitations worth knowing

A few things that don't work perfectly:

  • Login-gated web versions: If your ESP requires authentication to load the campaign web version (some enterprise HubSpot and Salesforce Marketing Cloud setups), the API can't access it without session cookies. Doable via the cookies parameter, but requires extra setup.
  • Personalization tokens in URLs: Some ESPs include contact-specific tokens in web version URLs, which means each subscriber sees a slightly different render. You'd typically capture the generic preview URL (without a contact token) for archive purposes.
  • Web version propagation delay: A freshly sent campaign's web version URL may 404 for a few seconds after the send event. A short delay in your automation (5–10 seconds) handles this in most cases.
  • This isn't a pre-send test: The API captures what's live at the URL. For testing how your HTML renders across different email clients before sending, see email previews.

For most email teams, the setup is a one-time script: read your ESP's campaign list, pass each web version URL to the API, save the output. After that, every send produces an archive automatically — no browser tabs, no manual exports, no gaps in documentation when a campaign URL eventually expires.

FAQ#

Any publicly accessible web version URL works — Mailchimp, Klaviyo, Constant Contact, ActiveCampaign, Brevo, and standard HubSpot sends all generate public links. Some enterprise setups (certain HubSpot configurations, Salesforce Marketing Cloud, Marketo) use token-protected or authentication-gated web versions, which require passing session cookies via the cookies parameter.

Email web versions frequently contain product images that lazy-load as the page scrolls. Without a delay, the API captures those images before they fire — resulting in blank or gray placeholders in the screenshot. A 2000ms delay lets the page settle after the full-page scroll. For particularly image-heavy campaigns or pages with animated banners, 3000ms is safer.

Most email templates are built for a 600–700px container. Setting width=700 produces a screenshot that looks like the campaign in a desktop inbox. For mobile views, use width=375 or device=mobile. Avoid full-desktop widths (1280px+) — email layouts are not designed for them and will render with large white margins on both sides.

The email-to-image feature uses the html parameter — you pass raw HTML markup directly to the API and it renders it as an image. This is for pre-send testing and rendering custom HTML. The email-marketing-screenshots use case uses the url parameter — you pass the public web version URL of a campaign that has already been sent. One is for rendering HTML you control; the other is for capturing live published pages.

Yes, with caveats. Adding certified=true returns a SHA-256 hash and ISO 8601 timestamp that prove when the screenshot was captured, which supports audit trail requirements under FINRA, SEC, and CAN-SPAM. It's not equivalent to a dedicated compliance archiving platform (no supervision, no eDiscovery, no retention policy enforcement), but it's a practical foundation for teams that need verifiable campaign documentation without enterprise software costs.

Yes. The API is stateless — each request is independent, so you can fire multiple requests concurrently. Using asyncio in Python or Promise.all in Node.js, a batch of 30 campaigns can be captured in under 15 seconds instead of 90 seconds sequential. See bulk screenshots for a parallel capture pattern. On the Free plan, rate limits apply; Starter and above allow higher concurrency.

Written by

Vitalii Holben

Co-founder and CEO of ScreenshotRun. Full-stack developer building screenshot API tools that help developers capture, automate, and monitor web pages at scale. Based in Europe, shipping code since 201...

Screenshot API in a few clicks

Free plan includes 200 screenshots every month. No credit card required.

Sign up → copy your key → make your first call