To put a chart in an email, send it as a PNG image: either hosted at an HTTPS URL or attached to the message and referenced by Content-ID. Set its width and height in the HTML, export it at twice that size for sharp screens, bake in a solid background, and write alt text that states the number the chart is about.

Interactive charts (Chart.js, D3, Highcharts and the rest) need JavaScript and a canvas or live SVG. Email clients run neither, so the chart that looks perfect in your dashboard arrives as an empty box.

Why JavaScript charts don’t render

Email HTML is a restricted dialect. Clients strip scripts, ignore much of CSS and each has its own rendering engine:

  • Outlook for Windows (classic) renders mail with Microsoft Word’s engine. Microsoft’s documentation for that engine lists script and object among the elements it does not support. The new Outlook for Windows is built on WebView2 and Outlook on the web instead, so the same message can look different in the two.
  • Gmail supports <style> blocks and “standard CSS”, but “might ignore unsupported CSS properties and selectors” (Gmail CSS support). Its documentation is about CSS; scripts are not part of what it renders.
  • Inline <svg> isn’t supported by Gmail or any Outlook for Windows version, according to Can I email, a community-run test database.

That leaves images. The image has to carry the whole chart.

PNG, not SVG

SVG would be ideal: small, sharp at any size. Support says otherwise. In Can I email’s tests of linked SVG images (last run September 2026):

  • Gmail now displays a linked SVG, but rasterizes it to PNG first. Before that, Gmail on the web didn’t show SVG at all, and its apps showed it only for non-Google accounts.
  • Outlook for Windows 2007 to 2016 doesn’t display SVG; Outlook 2019 and Outlook.com do.
  • Apple Mail displays SVG on recent versions (macOS 14 and iOS 15 onwards).

A PNG works in every one of those clients. Use PNG for line and bar charts (sharp edges, few colors, small files) and JPEG only for photographic content, which charts are not.

Three ways to include the image

  • A hosted image, <img src="https://…/chart.png">. The smallest email, and you can fix the image after sending. It shows only when the client loads remote images, and the URL must stay up as long as people may open the message.
  • An attachment referenced by Content-ID, a multipart/related message with <img src="cid:…">. Self-contained, with no server to run. The email is larger, and some clients also list the image as an attachment.
  • A base64 data URI, <img src="data:image/png;base64,…">. One self-contained HTML string, but Gmail doesn’t display it (Can I email).

Most reporting emails should use a hosted image or a Content-ID attachment. Avoid data URIs.

Hosted images and image blocking

Gmail shows images by default: “By default, when you get an email with an image, you’ll see the image automatically”, although users can switch to “Ask before displaying external images”, and Gmail hides images in messages it finds suspicious (Gmail Help). Gmail also fetches images through Google’s own proxy servers rather than from the reader’s device (Gmail blog), so an image that needs cookies, a login or an internal IP address won’t load (Google Workspace Admin Help).

Classic Outlook is the opposite: it “is configured by default to block automatic picture downloads from the Internet” (Microsoft Support). Many of your readers will see the alt text first. Plan for that (below).

For a hosted chart image: serve it over HTTPS from a URL that needs no login, give each report its own URL (so last week’s email doesn’t silently change), and keep it online for as long as people might reopen the message.

Content-ID attachments, with Python

The standard library builds a multipart/related message for you. This is adapted from the Python documentation’s own example:

from email.message import EmailMessage
from email.utils import make_msgid

msg = EmailMessage()
msg["Subject"] = "Weekly signups"
msg["From"] = "reports@example.com"
msg["To"] = "team@example.com"
msg.set_content("Signups this week: 1,240 (up 12%). The chart is in the HTML version of this email.")

chart_cid = make_msgid(domain="example.com")
msg.add_alternative(f"""\
<html>
  <body>
    <p>Signups this week: <strong>1,240</strong> (up 12%).</p>
    <img src="cid:{chart_cid[1:-1]}" width="600" height="300"
         alt="Bar chart of weekly signups: 1,240 this week, up 12% on last week."
         style="display:block; max-width:100%; height:auto;">
  </body>
</html>
""", subtype="html")

with open("chart.png", "rb") as f:
    msg.get_payload()[1].add_related(f.read(), "image", "png", cid=chart_cid, disposition="inline", filename="signups.png")

with open("weekly.eml", "wb") as f:
    f.write(bytes(msg))

The resulting message has the structure clients expect: multipart/alternative holding a plain-text part and a multipart/related part, which in turn holds the HTML and the PNG with Content-Disposition: inline and a matching Content-ID (the angle brackets are stripped for the cid: URL). Send it with smtplib or hand the bytes to your email provider’s API.

Size it for sharp screens

Charts drawn at exactly their display size look soft on high-density screens, which most phones and many laptops have. Export the PNG at twice the size you display it, and set the display size in the HTML:

  • Display width 600px (a common width for email layouts): export 1200px wide.
  • Set width="600" and height="300" as attributes, not only in CSS. Classic Outlook’s engine supports both, but attributes are the safe default across clients.
  • Add style="display:block; max-width:100%; height:auto;" so the image shrinks on narrow screens and doesn’t leave a gap under it.

Keep the file small. Charts compress well as PNG when they use flat colors; a 1200×600 bar chart is typically tens of kilobytes. Avoid gradients and shadows, which bloat the file and blur at small sizes.

Make it survive dark mode

Dark mode in email is inconsistent. Outlook’s dark mode “provides a black background (instead of white) for the message window” and changes message colors, with a button to view the original formatting (Microsoft Support). Apple Mail on macOS honors the color-scheme meta tag, while Gmail’s apps support only “light only” and Gmail on the web doesn’t support it at all (Can I email). You can’t control what each client does to your HTML, but you can control the image:

  • Never send a transparent PNG. Dark axis labels on a transparent background vanish when a client paints the background black. Bake a solid background into the image.
  • Add padding inside the image, in the same background color, so the chart reads as a card rather than a rectangle pasted onto a dark page.
  • Use colors that work on both: mid-tone series colors with enough contrast against the chart’s own background, and text inside the image rather than in surrounding HTML that dark mode may recolor.
  • Test in a dark client before the first send. Outlook and Apple Mail behave differently, and both differ from Gmail.

Write alt text that carries the number

When images are blocked, the alt text is the chart. Describe the conclusion, not the shape:

  • Weak: alt="Chart" or alt="Bar chart of signups".
  • Useful: alt="Weekly signups: 1,240 this week, up 12% on last week."

Then put the key number in live HTML text next to the image as well, as the example above does. Readers who skim, screen reader users and anyone with images off all get the point, and the chart becomes the supporting detail it should be.

Checklist

  1. PNG, exported at 2× the display size, with a solid background and inner padding.
  2. width and height attributes set to the display size; max-width:100%; height:auto for small screens.
  3. Hosted at a stable HTTPS URL that needs no login, or attached with a Content-ID. No data URIs.
  4. Alt text that states the conclusion, and the same number in the email’s text.
  5. Tested in at least Gmail, Outlook and Apple Mail, in light and dark mode.

Making the image

You need something that turns data into a PNG without a browser in the loop:

  • Chart Link: paste CSV or JSON, preview the chart and export a PNG or SVG. It runs in your browser, so the data never leaves it. Hosted chart image links, which would let an email reference an always-current URL, are in early access.
  • A script: if your report is already generated by code, render the chart on the server. Chart.js to PNG shows how in Node.js, including fonts and backgrounds.

The same image works elsewhere: see sending charts to Slack and charts in PDFs.

Sources

All sources were checked on October 14, 2026. Can I email is a community-run test database rather than a vendor source; its test dates are given where they matter.