Charts in Email
End-to-end examples for embedding charts in HTML emails with Resend, Nodemailer, or any SMTP provider. Every example is copy-paste runnable.
How it works
Email clients load images with <img src="URL">. Chart-Output's GET endpoint renders and returns a chart image when a URL is requested — no JavaScript, no iframes. Just an image URL.
Because browsers and email clients cannot send Authorization headers with image requests, pass your API key as a ?key= query parameter.
Always use PNG for email. It has the broadest support across Gmail, Outlook, Apple Mail, and Yahoo. See the format compatibility table.
Simple embed (GET endpoint)
For charts with fewer than ~8 data points, a direct GET URL is the fastest path:
<img
src="https://chart-output.com/api/v1/render?key=pk_live_YOUR_KEY&type=bar&labels=Jan,Feb,Mar,Apr&data=12000,15000,18000,22000&format=png&width=600&height=300"
alt="Monthly revenue"
width="600"
height="300"
style="display:block;max-width:100%;height:auto;"
/>Keep URLs under 2000 characters. For complex specs, use POST + returnUrl (see below).
Resend
curl -X POST https://api.resend.com/emails \
-H "Authorization: Bearer re_YOUR_RESEND_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "reports@acme.com",
"to": ["user@example.com"],
"subject": "Your monthly report",
"html": "<img src=\"https://chart-output.com/api/v1/render?key=pk_live_YOUR_KEY&type=bar&labels=Jan,Feb,Mar,Apr&data=12000,15000,18000,22000&format=png&width=600&height=300\" alt=\"Revenue chart\" width=\"600\" height=\"300\" />"
}'Nodemailer / SMTP
# Build the chart URL and use it in any SMTP tool
CHART_URL="https://chart-output.com/api/v1/render?key=pk_live_YOUR_KEY&type=line&labels=Jan,Feb,Mar,Apr&data=100,115,108,130&format=png&width=600&height=300"
echo "Embed: <img src=\"$CHART_URL\" />" POST + returnUrl (complex specs)
For specs that exceed 2000 characters, POST the spec with returnUrl: true. You get back a stable CDN URL to embed in your email:
# POST and capture the URL
RESPONSE=$(curl -s -X POST https://chart-output.com/api/v1/render \
-H "Authorization: Bearer pk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"bar","width":600,"height":300,"format":"png","returnUrl":true,"data":{"labels":["Jan","Feb","Mar"],"datasets":[{"data":[12000,15000,18000]}]}}')
URL=$(echo $RESPONSE | python3 -c "import sys,json; print(json.load(sys.stdin)['url'])")
echo "Embed URL: $URL"Full card in email
Use padding: "email" to apply the email-optimized layout padding preset. Render at scale: 2 for retina output, with logical width/height attributes on the <img> tag.
{
"type": "line",
"brandKitId": "obsidian",
"width": 600,
"height": 360,
"format": "png",
"scale": 2,
"padding": "email",
"backgroundColor": "#0d1117",
"borderRadius": 12,
"border": { "color": "rgba(255,255,255,0.08)", "width": 1 },
"header": {
"eyebrow": "MONTHLY REPORT",
"title": "$22,000",
"subtitle": "↑ 22% from last month"
},
"theme": { "textPrimary": "#f0f6fc", "textSecondary": "#6e7681" },
"data": {
"labels": ["Jan", "Feb", "Mar", "Apr"],
"datasets": [{
"label": "Revenue",
"data": [12000, 15000, 18000, 22000],
"borderColor": "#6ee7b7",
"backgroundColor": "rgba(110,231,183,0.07)",
"fill": true,
"tension": 0.4,
"borderWidth": 2,
"pointRadius": 0
}]
},
"footer": {
"left": "acme.com",
"right": "April 2026",
"borderTop": true
}
}POST this spec with returnUrl: true to get a CDN URL, then embed it with <img src="URL" width="600" height="360" />.
Troubleshooting
| Symptom | Fix |
|---|---|
| Image blocked on first open | Normal — email clients block images by default. Users click "Display images" once, then see them in all future emails. |
| Broken image in Outlook desktop | Check that your URL uses HTTPS. Verify the URL works in a browser before sending. |
| URL too long (> 2000 chars) | Use POST + returnUrl for complex specs instead of GET query params. |
| Distorted chart (labels/data mismatch) | Verify labels and data arrays have the same length. Test the URL directly in a browser. |
| 401 error in image | You cannot use Authorization headers in <img> tags. Use ?key= in the URL instead. |