Chart.js in Email: Generate a Static Image
Weekly digest emails, usage reports, invoice summaries — a lot of transactional email wants to show a chart. The problem: email clients don't execute JavaScript, so the Chart.js canvas that renders fine on your dashboard is simply invisible in an inbox. Gmail, Outlook, and Apple Mail all strip scripts for security reasons, full stop.
The fix is straightforward once you know the constraint: render the chart to a static image ahead of time, then embed it like any other image.
Why this trips people up
Developers new to email-with-charts usually try one of two things first, and both have real limitations:
- Screenshotting the live dashboard — fragile, requires a headless browser per email send, and doesn't scale if you're sending hundreds or thousands of personalized emails.
- Client-side chart libraries built for email — mostly nonexistent, because the constraint isn't a library problem, it's that email clients don't run JS at all.
The actual answer is simpler than either: generate the image server-side, once, at send time (or ahead of time), and reference it as a normal <img> tag.
The pattern
const chartResponse = await fetch("https://www.chart-output.com/api/v1/render", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.CHART_OUTPUT_API_KEY}`
},
body: JSON.stringify({
type: "line",
data: {
labels: ["Week 1", "Week 2", "Week 3", "Week 4"],
datasets: [{
label: "Active Users",
data: [420, 512, 489, 601]
}]
},
format: "png",
returnUrl: true
})
});
const { url } = await chartResponse.json();Because returnUrl: true is set, the response is a CDN-hosted image URL rather than raw bytes — exactly what you want for email, since you can drop it straight into your HTML template:
<img src="{{ chartUrl }}" alt="Weekly active users" width="600" style="display:block;" />Email-specific things worth knowing
- PNG over SVG. Email client support for SVG is inconsistent (Outlook in particular has historically had issues). PNG is the safe universal choice.
- Keep it visually quiet. Newsletter charts compete with the surrounding copy, so less chrome usually reads better. This email-oriented line chart in the playground is a good starting point — no grid clutter, solid white background, sized to fit a 600px email column. Switch the preview to the Email tab to see it inside a mock client.
- Set explicit width/height. Email clients don't always respect CSS sizing reliably — set
widthas an HTML attribute on the<img>tag itself, not just in a stylesheet. - Dark mode. Some clients invert images or apply a dark background behind transparent PNGs, and email-client support for conditional image swapping (
<picture>, media queries) is inconsistent enough that it's not a reliable default. The safer approach: render your chart on a solid, non-transparent background rather than a transparent one, so there's no background color for a client to guess at or invert. If you maintain separate light/dark email templates already, render a matching chart variant for each rather than trying to swap images within a single template. - Pre-render vs render-per-send. For a shared report (same chart to many recipients), render once and reuse the URL. For personalized data (e.g. "your usage this week"), you'll render per recipient — this is where sync rendering's ~133ms response time matters, since it needs to fit inside your send pipeline without becoming the bottleneck.
Card composition for a polished look
Rather than a bare chart image, wrapping it in a card — header, KPI numbers, footer — often reads better in an email context than a chart alone. Card composition renders that whole block as one image, so you're not fighting email HTML layout quirks to align a chart next to text.
For the full implementation walkthrough, including brand kit theming for on-brand emails, see the Charts in Email guide.