Docs menu
Charts in React Email
Put a chart in a React Email template with the SDK's ChartImage or a keyless URL, then send it with Resend or any provider.
React Email builds emails from React components. A Chartshot chart goes in as an image: with a key, the SDK's ChartImage signs its URL as the email renders; without one, a keyless chart URL goes in react-email's Img.
| Way | Badge | Plan |
|---|---|---|
ChartImage with a signed URL | None | Pro and up |
A keyless URL in Img | Chartshot badge | No account |
Start from a template
chartshot-react-email is a weekly digest email with MRR and churn tiles, an MRR trend and signups by day, in light and dark. It uses keyless URLs until you add a key, so it runs straight after npm install. The steps below show how it works.
Install
npm install react-email @lglab/chartshotReact Email 6 ships its components and render from the react-email package. The older @react-email/components package is deprecated.
Sign the URL with ChartImage
Create a key on the Keys page and keep its secret key and public id in your server's environment, as CHARTSHOT_SECRET_KEY and CHARTSHOT_PUBLIC_ID. Make one client, in a file such as charts.ts:
import { Charts } from "@lglab/chartshot";
export const charts = new Charts({
secretKey: process.env.CHARTSHOT_SECRET_KEY!,
publicId: process.env.CHARTSHOT_PUBLIC_ID,
});Then put ChartImage in the email, here weekly-report.tsx:
import { ChartImage } from "@lglab/chartshot/react";
import { Body, Html, Text } from "react-email";
import { charts } from "./charts";
export default function WeeklyReport({ signups }: { signups: number[] }) {
return (
<Html>
<Body>
<Text>Here are this week's signups.</Text>
<ChartImage
charts={charts}
spec={{
type: "bar",
preset: "email",
data: {
labels: ["Mon", "Tue", "Wed", "Thu", "Fri"],
series: [{ name: "Signups", values: signups }],
},
options: { title: "Weekly signups" },
}}
/>
</Body>
</Html>
);
}ChartImage renders an <img> with a signed URL, the chart's width and height, and alt text such as "Bar chart: Weekly signups". It signs without a request, so it works wherever react-email renders on your server. The chart has no badge and counts against your plan's renders.
Signed URLs come with Pro and up. On Free, a signed URL draws an error image that says so. Each URL works for 90 days unless you pass expiresIn; the SDK page has the options.
Use a keyless URL
A chart URL without a key works for anyone, with no account. Build the URL from the spec and pass it to Img with the chart's size and alt text:
import { Img } from "react-email";
const spec = {
"type": "bar",
"theme": "midnight",
"data": {
"labels": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
"series": [
{
"name": "Signups",
"values": [142, 168, 155, 189, 204, 96, 88]
}
]
},
"options": {
"title": "Signups this week",
"valueLabels": true,
"grid": false,
"yAxis": {
"hidden": true
}
},
"preset": "email"
};
const c = Buffer.from(JSON.stringify(spec)).toString("base64url");
const url = `https://api.chartshot.dev/v1/chart?c=${c}`;
export function SignupsChart() {
return <Img src={url} width="600" height="300" alt="Signups this week" />;
}Keyless charts carry the Chartshot badge, and each IP address can draw a limited number a minute. Limits on keyless URLs has the numbers.
Send it
With Resend, pass the email to react. Resend renders it to HTML on your server:
import { Resend } from "resend";
import WeeklyReport from "./weekly-report";
const resend = new Resend(process.env.RESEND_API_KEY);
await resend.emails.send({
from: "Reports <reports@example.com>",
to: ["you@example.com"],
subject: "This week's signups",
react: WeeklyReport({ signups: [12, 30, 21, 44, 38] }),
});For any other provider, render the HTML yourself with render and send that:
import { render } from "react-email";
const html = await render(<WeeklyReport signups={[12, 30, 21, 44, 38]} />);Chartshot draws the chart when the email client loads the image, not when the email renders. The URL holds the data, so the email shows the numbers it was sent with.
More on charts in email
Charts in email covers the email preset, why PNG, the theme's background in dark mode, and the playground's Gmail and Outlook preview.