Rendex
Recipe

Email an HTML Report as a PNG Attachment in n8n

Build the report as HTML in n8n, render it to a PNG with Rendex, and email it as an attachment on a schedule. Your team gets a clean visual report in their inbox — no headless browser, no glue code, no design tool.

Last updated 2026-06-02

The Problem

You want a recurring visual report — a weekly KPI summary, a daily sales digest, a status snapshot — delivered straight to inboxes. n8n can pull the numbers and build the HTML easily, but turning that HTML into an image to attach is the hard part: there's no native 'render this HTML to an image' step, and self-hosting Puppeteer in an n8n worker means a Chrome binary to install, keep warm, and watch for memory leaks. Emailing a raw HTML body instead is fragile — clients strip CSS, break layouts, and mangle tables, so the report rarely looks the way you designed it.

The Solution

Add the Rendex node between your data and your email step. n8n assembles the report as an HTML string (optionally with {{placeholders}} and a data object), the Rendex node renders it to a PNG and returns it as binary on the item, and a Gmail or SMTP node attaches that binary and sends it. The image looks identical in every inbox because it's a rasterized render, not live HTML the client can re-style. It runs on a Schedule trigger for recurring delivery, uses no headless browser, and stays on Rendex's free tier. The same pattern works in Make with an HTTP module plus an email module.

How the Workflow Runs

Schedule

An n8n Schedule trigger fires the run (e.g. weekly Monday 08:00).

Build report HTML

A Set/Code node assembles the report HTML from your data source (Sheets, Postgres, an API).

Rendex

The Rendex node renders the HTML to a PNG and returns it as binary on the item.

Email attachment

A Gmail/SMTP node attaches the PNG binary and sends the report to your team.

Input → Rendered Output

Left: an n8n flow — Schedule at 08:00, a Set node building report HTML, the Rendex render node, and a Gmail node — with the KPIs merged from a DB node. Right: the rendered KPI report PNG attached to the email, showing a heading, three metric cards (Signups, MRR, Churn), and a small brand-orange bar chart.

Rendered by Rendex

What You Need

  • A Rendex API key — the free tier includes 100 renders/month with no card required.
  • n8n (cloud or self-hosted) with the Rendex community node installed — or Make with an HTTP module.
  • A data source for the report (Google Sheets, Postgres, an HTTP API) and a Set/Code node that builds the report HTML string.
  • An email node (Gmail, SMTP, or your provider) configured to send to your recipients.

What This Recipe Uses

HTML to PNG

The Rendex node renders your report HTML to a PNG via /v1/screenshot and returns it as binary — no headless browser inside n8n.

Attach & send

The render lands as binary on the item, so a Gmail/SMTP node attaches the PNG and sends it with no extra encoding step.

Scheduled delivery

A Schedule trigger drives recurring reports — weekly KPIs, a daily digest — without anyone pressing a button.

n8n or Make

Same render call either way: the Rendex n8n node, or a Make HTTP module that posts the identical JSON body before the email module.

Build It

rendex-report-node.json
{
  "parameters": {
    "resource": "screenshot",
    "operation": "capture",
    "source": "html",
    "html": "={{ $json.reportHtml }}",
    "format": "png",
    "width": 1000,
    "fullPage": true,
    "deviceScaleFactor": 2
  },
  "name": "Rendex — Render Report PNG",
  "type": "n8n-nodes-rendex.rendex",
  "typeVersion": 1
}
build-report-html.js
// Code node placed BEFORE the Rendex node. Assembles the report HTML
// string from upstream data and puts it on $json.reportHtml for the next node.
const rows = $input.all().map((item) => item.json); // e.g. from Sheets/Postgres

const cards = [
  { label: "MRR",        value: "€48,250" },
  { label: "New signups", value: "312"     },
  { label: "Churn",      value: "1.8%"     },
];

const cardHtml = cards
  .map(
    (c) => `<div style="flex:1;padding:20px;border:1px solid #e2e8f0;border-radius:12px">
      <div style="font-size:12px;text-transform:uppercase;color:#64748b">${c.label}</div>
      <div style="font-size:32px;font-weight:700;color:#0f172a">${c.value}</div>
    </div>`,
  )
  .join("");

const tableRows = rows
  .map((r) => `<tr><td style="padding:8px 12px;border-bottom:1px solid #f1f5f9">${r.name}</td>
    <td style="padding:8px 12px;border-bottom:1px solid #f1f5f9;text-align:right">${r.amount}</td></tr>`)
  .join("");

const reportHtml = `<!doctype html><html><head><meta charset="utf-8">
<style>body{font-family:system-ui,sans-serif;color:#0f172a;margin:0;padding:40px;width:1000px}
h1{color:#ea580c;font-size:26px;margin:0 0 24px}
table{width:100%;border-collapse:collapse;margin-top:28px}</style></head>
<body><h1>Weekly Performance — ${new Date().toLocaleDateString()}</h1>
<div style="display:flex;gap:16px">${cardHtml}</div>
<table><tbody>${tableRows}</tbody></table></body></html>`;

return [{ json: { reportHtml } }];
render-report.sh
# In Make, use an HTTP "Make a request" module BEFORE the email module with
# this exact body. The response is the PNG binary you attach to the email.
curl -X POST https://api.rendex.dev/v1/screenshot \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  --output weekly-report.png \
  -d '{
    "html": "<!doctype html><html><head><style>body{font-family:system-ui,sans-serif;color:#0f172a;padding:40px;width:1000px}h1{color:#ea580c;margin:0 0 24px}.cards{display:flex;gap:16px}.card{flex:1;padding:20px;border:1px solid #e2e8f0;border-radius:12px}.lbl{font-size:12px;text-transform:uppercase;color:#64748b}.val{font-size:32px;font-weight:700}</style></head><body><h1>Weekly Performance — {{week}}</h1><div class=\"cards\"><div class=\"card\"><div class=\"lbl\">MRR</div><div class=\"val\">{{mrr}}</div></div><div class=\"card\"><div class=\"lbl\">New signups</div><div class=\"val\">{{signups}}</div></div><div class=\"card\"><div class=\"lbl\">Churn</div><div class=\"val\">{{churn}}</div></div></div></body></html>",
    "data": {
      "week": "2026-06-01",
      "mrr": "€48,250",
      "signups": "312",
      "churn": "1.8%"
    },
    "format": "png",
    "width": 1000,
    "fullPage": true,
    "deviceScaleFactor": 2
  }'

100 free API calls/month — no credit card required. Get your API key and start building.

Reach for this workflow when a report should arrive as a picture, not a webpage — a Monday KPI summary for the leadership channel, a daily numbers digest for a sales team, a per-client status snapshot. The friction it removes is real: n8n is great at gathering data and stitching HTML, but it has no built-in way to turn that HTML into an image, and standing up a headless browser inside a worker is exactly the infrastructure most no-code builders are trying to avoid. Dropping the Rendex node into the middle keeps the whole flow on the canvas — Schedule, build HTML, render, email — with no service to deploy. Rendering to a PNG also fixes the email-client problem: a rasterized image looks the same in Gmail, Outlook, and Apple Mail, where a raw HTML body would have its CSS stripped and its tables broken. Keep the report template in a Set or Code node so a design change is one edit, pass full-page on so a long table isn't clipped, and render at 2x for crisp type. Use n8n if that's where your data already lives, or the identical Make pattern if your stack is there instead. It all runs on the free 100 renders a month, no card.

Frequently Asked Questions

Related Resources