Tips > Building Workflows

Generate Dynamic HTML Email Templates

Instead of fighting with the limited formatting options in n8n's email nodes, build your HTML in a Code node.

TipIntermediate2 min read

Instead of fighting with the limited formatting options in n8n's email nodes, build your HTML in a Code node. This gives you full control over styling, conditional sections, and dynamic table generation.

Real-world example: A daily sales report email that includes a summary header, a table of top deals, and conditional warning banners when targets are missed.

// Mode: Run Once for All Items
const deals = $input.all().map(i => i.json);
const totalRevenue = deals.reduce((s, d) => s + d.amount, 0);
const target = 50000;
const hitTarget = totalRevenue >= target;

const tableRows = deals
  .sort((a, b) => b.amount - a.amount)
  .slice(0, 10)
  .map(d => `
    <tr>
      <td style="padding:8px;border-bottom:1px solid #ddd;">${d.dealName}</td>
      <td style="padding:8px;border-bottom:1px solid #ddd;">${d.rep}</td>
      <td style="padding:8px;border-bottom:1px solid #ddd;text-align:right;">
        $${d.amount.toLocaleString()}
      </td>
      <td style="padding:8px;border-bottom:1px solid #ddd;">${d.stage}</td>
    </tr>
  `).join('');

const warningBanner = hitTarget ? '' : `
  <div style="background:#c0392b;color:#fff;padding:12px;margin:16px 0;
              border-radius:4px;text-align:center;">
    Revenue is $${(target - totalRevenue).toLocaleString()} below daily target.
    Review pipeline immediately.

`;

const html = `
<div style="font-family:Arial,sans-serif;max-width:680px;margin:0 auto;">
  <h2 style="color:#2c3e50;">Daily Sales Report</h2>
  <p>Report generated: ${new Date().toISOString().slice(0, 10)}</p>
  ${warningBanner}
  <div style="background:#ecf0f1;padding:16px;border-radius:4px;margin:16px 0;">
    <strong>Total Revenue:</strong> $${totalRevenue.toLocaleString()} /
    $${target.toLocaleString()} target
    (${((totalRevenue / target) * 100).toFixed(1)}%)

  <h3>Top 10 Deals</h3>
  <table style="width:100%;border-collapse:collapse;">
    <thead>
      <tr style="background:#34495e;color:#fff;">
        <th style="padding:8px;text-align:left;">Deal</th>
        <th style="padding:8px;text-align:left;">Rep</th>
        <th style="padding:8px;text-align:right;">Amount</th>
        <th style="padding:8px;text-align:left;">Stage</th>
      </tr>
    </thead>
    <tbody>${tableRows}</tbody>
  </table>

`;

return [{ json: { html, subject: `Sales Report - $${totalRevenue.toLocaleString()} ${hitTarget ? '(Target Met)' : '(Below Target)'}` } }];
```text
Feed the `html` output into the Send Email node's HTML body field and the `subject` into the subject line.

**Related:** [Use Edit Fields in "Map Each" Mode for Simple Renames](../data-transformation/01-use-edit-fields-in-map-each-mode-for-simple-renames.md) | [Configure Payload Size and Binary Data Mode for Large Files](../performance-and-large-files/01-configure-payload-size-and-binary-data-mode-for-large-files.md)

Want this running in your stack?

I build production n8n and Cloudflare automation for teams — the same engineering behind HarperFlow. Fixed-price, escrow-protected, US-based.