Embed inline charts in a status email
Render a Sheets chart as an image inside the email body, not as an attachment.
Published May 12, 2026
A status email with the chart as an attachment is a status email half the recipients won’t read. The pipeline picture is the whole point of the message, but it’s hidden behind a click — and on a phone, that click opens a separate viewer and breaks the flow entirely.
Northwind’s Friday status mail solves this by putting the chart in the body. This script grabs a live chart straight from a Sheet, renders it as a PNG, and embeds it inline so partners see the pipeline the instant they open the email — no attachment, no click, no manual export. The chart is whatever you’ve already built in the spreadsheet, so it stays in sync with the data on its own.
What you’ll need
- A Google Sheet with at least one chart on its first tab. The chart can be any type — the script renders whatever it finds.
- The sheet’s ID, pasted into
PIPELINE_SHEETin the config block. - The recipient address (a person or a group) for the status mail.
- Edit access to the Sheet, so the script can read the chart object.
The script
// The spreadsheet whose chart gets embedded in the email.
const PIPELINE_SHEET = '1abcPipelineSheetId';
// Who receives the status mail, and the subject line.
const RECIPIENT = '[email protected]';
const SUBJECT = 'Pipeline — this Friday';
// The content ID that links the HTML <img> to the inline image.
// It just has to match in both places; the exact string is arbitrary.
const CHART_CID = 'pipelineChart';
/**
* Reads the first chart from the pipeline sheet, renders it as a PNG,
* and emails it embedded inline in the message body.
*/
function emailStatusWithChart() {
// 1. Open the sheet and grab its first chart.
const sheet = SpreadsheetApp.openById(PIPELINE_SHEET).getSheets()[0];
const charts = sheet.getCharts();
// 2. Bail out cleanly if there's no chart to send.
if (charts.length === 0) {
Logger.log('No chart on the sheet — nothing to send.');
return;
}
// 3. Render the chart to a PNG blob. This is a live render of the
// chart as it currently looks — no manual export step.
const blob = charts[0].getAs('image/png').setName('pipeline.png');
// 4. Build the HTML body. The <img> pulls from the inline image
// via cid: plus the matching content ID.
const html = `
<p>Northwind pipeline snapshot:</p>
<img src="cid:${CHART_CID}" alt="Pipeline">
<p>— Awadesh</p>
`;
// 5. Send it. The plain-text body is empty; htmlBody is the real
// message, and inlineImages maps the CID to the blob.
GmailApp.sendEmail(RECIPIENT, SUBJECT, '', {
htmlBody: html,
inlineImages: { [CHART_CID]: blob },
});
Logger.log('Status email sent to ' + RECIPIENT);
}
How it works
emailStatusWithChartopens the pipeline sheet and callsgetCharts()on its first tab, which returns every chart embedded on that sheet.- If there are no charts, it logs a message and stops — no point sending an email with a broken image in it.
getAs('image/png')renders the first chart to a PNG blob. This is a fresh render of the chart in its current state, so the email always reflects the latest data without anyone exporting anything by hand.- The HTML body references the image with
<img src="cid:pipelineChart">. Thecid:scheme means “look this up in the inline images for this message” rather than fetching a URL. GmailApp.sendEmailis called with an empty plain-text body and an options object.htmlBodycarries the real message;inlineImagesis a map from content ID to blob. BecauseCHART_CIDis used as a computed key, the map key and thecid:reference can never drift apart.- Gmail attaches the blob as a hidden inline part and wires it to the
<img>, so the chart appears in the body itself, not as a downloadable file.
Example run
Imagine the pipeline sheet holds a column chart of deal value by stage:
| Stage | Value (£k) |
|---|---|
| Lead | 40 |
| Proposal | 95 |
| Negotiation | 60 |
| Won | 120 |
After a run, [email protected] receives an email titled
Pipeline — this Friday. The body reads “Northwind pipeline snapshot:”,
followed by the column chart rendered directly in the message, then the sign-off
”— Awadesh”. On a phone or a laptop, the chart is visible immediately with no
attachment to open.
Trigger it
This is a weekly report, so schedule it for the end of the working week:
- In the Apps Script editor, open Triggers (the clock icon).
- Click Add Trigger.
- Choose
emailStatusWithChart, event source Time-driven, type Week timer, day Friday, and the 4pm–5pm slot. - Save, and approve the authorisation prompt the first time.
Watch out for
getCharts()returns charts in no guaranteed order. The script takescharts[0], so if the sheet has several charts, make sure the one you want is the only chart on that tab — or move it to its own dedicated sheet.- Inline images need an HTML body. If you pass a plain-text body and an
inlineImagesmap, the image silently won’t render —htmlBodyis required. - The CID is internal plumbing, not a URL. It just has to be identical in the
<img src="cid:...">and theinlineImageskey; using theCHART_CIDconstant for both keeps them locked together. - Chart rendering reflects the spreadsheet, including its theme and size. If the chart looks cramped in the email, resize it in the Sheet — there’s no way to rescale it from the script.
- A small number of email clients block inline images by default, the same as
they block remote images. Keep the
alttext meaningful so the message still makes sense if the chart doesn’t load.
Related
Send meeting follow-ups with the notes attached
After a Calendar event ends, email attendees the linked notes Doc automatically.
Updated May 19, 2026
Send HTML email from a Google Doc template
Use a styled Doc as the source for branded, on-brand HTML email — no design tool needed.
Updated May 5, 2026
Parse bank-alert emails into an expense ledger
Convert transaction alerts from Northwind's bank into categorised spend rows automatically.
Updated Apr 28, 2026
Generate a printable address book from contacts
Export Northwind's Google Contacts to a formatted Doc you can actually print.
Updated Apr 21, 2026
Email a weekly "what changed" report from a Sheet
Diff the Projects sheet week over week and email the team the rows that changed.
Updated Apr 14, 2026