DEV Community

Cover image for Email Charts with the ProvChart API
FSCSS for ProvChart

Posted on Originally published at chart.devtem.org

Email Charts with the ProvChart API

Most chart libraries assume a browser that runs JavaScript. Email clients don't give you that. If you want a chart in a weekly digest, you need something that renders without a script tag, or a step that turns a chart into an image before it ever reaches an inbox.

That's the problem ProvChart's API is built for. It returns HTML and CSS, generated server-side, and you decide from there whether to rasterize it into a PNG or embed a tightly controlled snippet. This post walks through the flow, the chart types that actually work in a digest, and a handful of practical tips for running this in production.

Why email is different

The inbox never needs to see your API key or your chart library. It only needs a static image and a link back to the live dashboard. Everything else, the data pull, the payload mapping, the API call, happens on your server or in a worker, before the send.

The flow

  1. Pull analytics for the period, from your API, warehouse, or database
  2. Map the rows into a ProvChart payload (type, series, axisX, theme)
  3. POST /api/v1/generate with your API key
  4. Turn the returned html and css into a PNG (or store the snippet)
  5. Embed the result in your email template with a CTA back to the dashboard

Mapping analytics to a payload

Keep a single mapper function so every report type shares the same shape:

function analyticsToPayload(report) {
  return {
    type: "line",
    series: [
      {
        name: "Revenue",
        color: "#8b7bff",
        points: report.revenueByDay.map(d => d.value)
      },
      {
        name: "Orders",
        color: "#4fd8c4",
        points: report.ordersByDay.map(d => d.value)
      }
    ],
    axisX: report.revenueByDay.map(d => d.label),
    theme: {
      bg: "#0c0a16",
      surface: "#1a1628",
      text: "#eae7f5",
      muted: "#837da0",
      grid: "#8b7bff",
      radius: "12px"
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

Swap the data source underneath it, but keep the function stable. Weekly, monthly, and alert emails all stay consistent this way.

Chart types that actually work in a digest

Use case Type Why
Trend over 7–30 days line / area Readable as a simple story
Channel or category mix bar Clear comparison
Goal or conversion gauge One number, high impact
Share of total stackedbar Good for mix breakdowns

Skip dense scatter plots or multi-combo charts in email. Save those for the product UI, where someone can actually zoom in and interact.

ProvChart bar chart example

Ten things worth doing

  1. Generate server-side. The send job or worker owns the API key, the payload, and the output. Never call the API from the client.
  2. Prefer images for email clients. The reliable path is: analytics → API → html/css → headless render (Playwright or Puppeteer) → PNG → CDN → <img> in the template. This works across Gmail, Outlook, and Apple Mail.
  3. Design for one glance. Each chart should answer one question: did revenue rise, which channel led. Cap it at one or two series and 7–14 data points. Put the headline number in the subject line or preheader.
  4. Cache by payload hash. Identical type, series, labels, and theme should produce identical output. Cache the html/css or the PNG so repeat sends don't burn quota.
  5. Check usage before bulk sends. Hit GET /api/v1/usage first. If remaining is low, drop the optional charts or fall back to number-only blocks rather than hitting MONTHLY_LIMIT_REACHED mid-campaign.
  6. Theme for email, not the admin UI. Use higher contrast and a background that matches your template. A dedicated theme object for digests versus in-app charts goes a long way.
  7. Always add a CTA. A simple "Full dashboard →" link under the chart. The email is the snapshot; the app is where people explore.
  8. Keep one layout per digest type. Hero metric, one trend chart, one breakdown, a few bullets, then the CTA. Change the data, not the structure.
  9. Validate before you generate. Empty series, non-numeric points, or oversized arrays waste generations. Enforce max points and required fields in the mapper itself.
  10. Proxy the key. Production frontends shouldn't hold the secret key at all. Route browser requests through your own backend before they reach ProvChart.

A minimal server-side example

const payload = analyticsToPayload(weeklyReport);

const res = await fetch("https://provchart-api.devtem.org/api/v1/generate", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": process.env.PROVCHART_API_KEY
  },
  body: JSON.stringify(payload)
});

const data = await res.json();
if (!data.success) throw new Error(data.error);

// Option A: rasterize data.html + data.css into a PNG, upload it, then <img src="...">
// Option B: store the snippet for a web version of the same report
Enter fullscreen mode Exit fullscreen mode

Do / Don't

Do

  • Generate server-side
  • Map analytics to a fixed payload shape
  • Cache by hash
  • Prefer line, bar, or gauge charts
  • Link back to the live dashboard

Don't

  • Call the API from the inbox
  • Build ad-hoc JSON for every send
  • Regenerate identical charts
  • Pack five dense series into a digest
  • Expect full interactivity in email

Next steps

Top comments (0)