Email Template
Three templates, used together or alone. Pick based on what the email is and which system generates it:
| Email is... |
Use |
Delivery |
| A notification, alert, or short status update |
A — Notification |
inline HTML, the email body itself |
| A generated report (analysis, briefing, summary) from a script or job |
B — Report |
standalone .html file, attach it or link it — don't paste it as the email body |
| A markdown digest generated by a workflow tool (weekly/daily briefings, newsletters) |
C — Inline digest |
rendered to light-card HTML and inlined directly as the body (safe — inline styles only, no CSS vars) |
A report email is usually both A and B (or C): template A's footer is appended to every send regardless of which body template produced the content — that's a shared footer helper's job, not a per-template choice. For script/job artifacts, template B stays an attachment/link per the note below. For workflow-tool digests, the fleet can inline template C's full HTML as the body and still attach the archived .html file — both, not either/or.
Why not inline Template B into the email body? Template B relies on CSS custom properties (var(--x)), background-clip: text, and backdrop-filter — Gmail and Outlook strip or mangle all three. It renders correctly in a browser or PDF, not in an email client's DOM. Keep it as an attachment or a hosted link. Template C avoids this problem entirely by using only inline style="..." attributes — that's why it's safe to inline.
Template A — Notification / alert
Plain inline styles only, no <style> block, no CSS variables — this is what survives every email client.
<p style="margin:0 0 14px;font-size:15px;line-height:1.6;color:#1a1a1a;font-family:-apple-system,'Segoe UI',Helvetica,Arial,sans-serif">
{body_paragraph_1}
</p>
<p style="margin:0 0 14px;font-size:15px;line-height:1.6;color:#1a1a1a;font-family:-apple-system,'Segoe UI',Helvetica,Arial,sans-serif">
{body_paragraph_2}
</p>
<!-- optional: one call-to-action link, omit the block if there is none -->
<p style="margin:20px 0 0;font-size:14px;line-height:1.5">
<a href="{cta_url}" style="color:#0891b2;font-weight:600;text-decoration:none">{cta_label} →</a>
</p>
<hr style="margin-top:24px;border:none;border-top:1px solid #ddd">
<p style="color:#888;font-size:12px;margin-top:8px">
<strong>{sender_display_name}</strong> · automation fleet ·
<a href="{home_url}" style="color:#888">Browse more →</a>
</p>
Rules that make this the house style, not just a style:
- Paragraphs, not one wrapped
<p>. Multi-line text loses its breaks in a single tag — split on blank lines into separate <p>, single newlines become <br>.
- One footer, every send.
<strong>{sender_display_name}</strong> · automation fleet · <a>Browse more →</a> — the bold name is the literal string a Gmail filter can match on, so pick one identity name per automation and never change it later.
#0891b2 for links, #888 for footer text. Keep both — they're the only two non-body colors in the template.
- Subject line:
{Report/Alert Name} — {date or week range}, e.g. Weekly Security Briefing — Week of 2026-08-23 to 2026-08-29, or {Job Name} failed — {date} for alerts. Tag every automation send with a label matching the job slug, if your mail provider supports labels.
- Sender identity: one
From display name per automation family (e.g. My Automation Fleet <alerts@example.com>), reused across every email that family sends — don't invent a new sender per report.
- Footer is provider/template-agnostic. A shared footer helper (whatever sends your mail) should append this block after the body regardless of which template (A, B, or C) produced the content above it.
Template C — Inline digest
For a workflow tool (n8n, Zapier, a scheduled script, etc.) that turns a fixed-structure Markdown briefing (# title / **Week of:** / ## sections / ### per-story headings / - bullets / --- rules) into an inlined HTML email. One conversion function (mdToHtml) can be reused across every digest workflow, each supplying its own accentColor.
<!DOCTYPE html><html><body style="font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;max-width:720px;margin:0 auto;padding:24px;background:#fafaf9;color:#1f2937;">
<div style="background:#ffffff;padding:32px;border-radius:12px;border:1px solid #e5e7eb;">
{body_html — h1/h2/h3/ul/p produced by mdToHtml(markdown)}
<div style="margin-top:32px;padding-top:16px;border-top:1px solid #e5e7eb;color:#9ca3af;font-size:12px;">Generated by {tool} · {date} · Items considered: {n}</div>
</div>
</body></html>
mdToHtml splits the Markdown on blank lines (\n{2,}) into blocks and converts each block by its first line only: # →<h1>, ## →<h2 style="color:{accentColor}...">, ### →<h3>, a block whose first line is - →<ul><li> (splits every - line in the block into an <li>), anything else→<p> with \n→<br>. Links [text](url) and **bold** are regex-replaced after block conversion.
Two known bugs in this converter shape — worth fixing at the source, not just documenting:
- Per-story detail lines don't listify. If a prompt template puts
### {headline} and its - **Source:** ... / - **Link:** ... / - **What happened:** ... bullets in the same block (no blank line between them), the block is classified by its first line and matches the ### branch — .replace(/^### (.+)$/m, '<h3>...</h3>') only replaces that first line, so the - bullets underneath fall through unconverted: raw text with literal - prefixes and no <br> between them (the <br> conversion only happens in the else/plain-paragraph branch). Fix: after the heading match, split the block's remaining lines and run them through the same - → <li> logic, or handle "heading + bullets" as its own block type.
--- rules render as literal text. A lone --- block doesn't match # , ## , ### , or /^- / (no space after the dashes), so it falls into the plain-<p> branch and renders as <p>---</p> instead of an <hr>. Fix: add a check for /^-{3,}$/ before the final else and emit <hr style="border:none;border-top:1px solid #e5e7eb;margin:20px 0;">.
Delivery can be two-stage rather than a single send: the workflow tool posts the inline HTML to your mail provider's API and separately archives the .html somewhere durable (a repo, a bucket); a downstream sender can then re-send the archived artifact as the actual email, with --html set to the same generated body and --file set to the archived copy as an attachment — appending Template A's footer at send time. That gets you both an inlined report body and an attached .html copy, not either/or.
Template B — Generated report
Full standalone HTML document: dark card on a soft radial-gradient page, markdown-shaped content styling. Same skeleton for every report — the CSS never changes, only the four banner strings and the body content.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{Report Title}</title>
<style>
:root {
--bg-page: #07090f;
--bg-wrapper: #0e1320;
--bg-elevated: #131a2c;
--bg-elevated-2: #182135;
--bg-deep: #050810;
--border: rgba(255,255,255,0.06);
--border-strong: rgba(255,255,255,0.10);
--text-primary: #e6edf7;
--text-secondary: #b3bdd1;
--text-muted: #7d899f;
--accent: #7dd3fc;
--accent-soft: #38bdf8;
--accent-warm: #f5c97a;
--gradient-banner: linear-gradient(135deg, #0a0f1f 0%, #14224a 40%, #1f3870 75%, #2a4f9c 100%);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Inter", "Helvetica Neue", Arial, sans-serif;
background:
radial-gradient(1200px 600px at 15% -10%, rgba(56,189,248,0.07), transparent 60%),
radial-gradient(900px 500px at 110% 10%, rgba(125,211,252,0.05), transparent 60%),
var(--bg-page);
color: var(--text-primary);
line-height: 1.6;
font-size: 16px;
padding: 28px 16px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.wrapper {
max-width: 880px;
margin: 0 auto;
background: var(--bg-wrapper);
border: 1px solid var(--border);
border-radius: 14px;
overflow: hidden;
box-shadow:
0 1px 0 rgba(255,255,255,0.04) inset,
0 30px 80px rgba(0,0,0,0.55),
0 8px 24px rgba(0,0,0,0.35);
}
.banner {
position: relative;
background: var(--gradient-banner);
color: var(--text-primary);
padding: 44px 40px 32px;
border-bottom: 1px solid var(--border-strong);
overflow: hidden;
}
.banner::before {
content: "";
position: absolute;
inset: 0;
background:
radial-gradient(600px 200px at 50% 0%, rgba(125,211,252,0.18), transparent 70%),
radial-gradient(400px 150px at 85% 100%, rgba(245,201,122,0.07), transparent 70%);
pointer-events: none;
}
.banner > * { position: relative; z-index: 1; }
.banner-eyebrow {
font-size: 11px;
font-weight: 600;
letter-spacing: 2px;
color: var(--accent);
text-transform: uppercase;
margin-bottom: 10px;
}
.banner-title {
font-size: 28px;
font-weight: 800;
line-height: 1.22;
margin-bottom: 8px;
letter-spacing: -0.4px;
background: linear-gradient(180deg, #ffffff 0%, #cfe1ff 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.banner-subtitle {
font-size: 14px;
color: var(--text-secondary);
font-weight: 400;
}
.banner-date {
display: inline-block;
margin-top: 16px;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.14);
border-radius: 20px;
padding: 5px 14px;
font-size: 12px;
color: var(--text-secondary);
backdrop-filter: blur(6px);
}
.content {
padding: 32px 40px 26px;
}
.content h2 {
color: var(--text-primary);
font-size: 20px;
font-weight: 700;
margin: 30px 0 14px;
padding-bottom: 8px;
border-bottom: 1px solid var(--border);
letter-spacing: -0.2px;
}
.content h2:first-child { margin-top: 0; }
.content h3 {
color: var(--accent);
font-size: 15px;
font-weight: 600;
margin: 20px 0 8px;
letter-spacing: 0.1px;
}
.content p { margin: 10px 0; color: var(--text-secondary); }
.content ul, .content ol { margin: 10px 0 10px 24px; color: var(--text-secondary); }
.content li { margin: 5px 0; }
.content li::marker { color: var(--accent); }
.content a {
color: var(--accent);
text-decoration: none;
border-bottom: 1px solid rgba(125,211,252,0.3);
transition: color 0.15s, border-color 0.15s;
}
.content a:hover { color: #bae6fd; border-bottom-color: var(--accent); }
.content code {
background: var(--bg-deep);
border: 1px solid var(--border);
padding: 1px 6px;
border-radius: 4px;
font-family: "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
font-size: 0.88em;
color: var(--accent-warm);
}
.content pre {
background: var(--bg-deep);
border: 1px solid var(--border);
color: var(--text-secondary);
padding: 14px 18px;
border-radius: 8px;
overflow-x: auto;
margin: 14px 0;
font-size: 13px;
}
.content pre code { background: none; border: none; padding: 0; color: inherit; }
.content blockquote {
border-left: 3px solid var(--accent);
background: var(--bg-elevated);
padding: 10px 18px;
margin: 14px 0;
color: var(--text-secondary);
font-style: italic;
border-radius: 0 6px 6px 0;
}
.content hr { border: none; border-top: 1px solid var(--border); margin: 22px 0; }
.content table {
border-collapse: separate;
border-spacing: 0;
width: 100%;
margin: 16px 0;
font-size: 14px;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
}
.content table th {
background: linear-gradient(180deg, #1c2540 0%, #131a2c 100%);
color: var(--text-primary);
text-align: left;
padding: 10px 12px;
font-weight: 600;
font-size: 12px;
letter-spacing: 0.4px;
text-transform: uppercase;
border-bottom: 1px solid var(--border-strong);
}
.content table td { padding: 9px 12px; border-bottom: 1px solid var(--border); color: var(--text-secondary); }
.content table tr:last-child td { border-bottom: none; }
.content table tr:nth-child(even) td { background: rgba(255,255,255,0.015); }
.content table tr:hover td { background: rgba(125,211,252,0.04); }
.content strong { color: var(--text-primary); font-weight: 600; }
.content em { color: var(--text-secondary); }
.content img {
display: block;
max-width: 100%;
height: auto;
margin: 20px auto;
background: #ffffff;
padding: 12px;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.45);
}
.footer {
background: var(--bg-deep);
border-top: 1px solid var(--border);
padding: 18px 40px;
font-size: 11px;
color: var(--text-muted);
text-align: center;
letter-spacing: 0.5px;
}
@media (max-width: 640px) {
body { padding: 0; }
.wrapper { border-radius: 0; box-shadow: none; border-left: none; border-right: none; }
.banner, .content, .footer { padding-left: 20px; padding-right: 20px; }
.banner-title { font-size: 24px; }
.content table { font-size: 12px; }
}
</style>
</head>
<body>
<div class="wrapper">
<header class="banner">
<div class="banner-eyebrow">{Eyebrow — e.g. the generating agent/product name}</div>
<div class="banner-title">{Report Title}</div>
<div class="banner-subtitle">{One-line subtitle}</div>
<div class="banner-date">{Date or date range}</div>
</header>
<main class="content">
{report_body_html — h2/h3/p/ul/ol/table/blockquote/hr/img, exactly what a markdown-to-HTML render of the report produces}
</main>
<footer class="footer">{Footer disclaimer — e.g. "For Educational Purposes Only"}</footer>
</div>
</body>
</html>
Rules:
- Only four things change per report:
<title>, banner-eyebrow, banner-title, banner-subtitle, banner-date, and the .content body. The <style> block is copy-pasted verbatim — don't redesign it per report.
.content is markdown output, not custom markup. Render the report body as plain h2/h3/p/ul/ol/table/blockquote/hr/img — the CSS already styles all of them. Don't hand-build stat cards or custom divs inside .content; the template doesn't have classes for them.
.banner-eyebrow is the product/agent name generating the report; swap it for the automation's own name.
- Footer is a one-line disclaimer or attribution, not the fleet footer from Template A — this document stands alone (opened directly, attached, or hosted), it doesn't need the "Browse more →" link.
Putting them together
Script/job reports (Template B):
- Generate the report as its own
.html file using Template B, named for what it is (e.g. Weekly_Security_Briefing.html), saved next to the automation's other outputs.
- Send the notification email using Template A as the body — one or two sentences on what's in the report, a CTA link if the report is hosted somewhere, or send the report file as an email attachment.
- Keep the subject line and label conventions from Template A even when the body is otherwise custom — that's what makes an inbox filter and a fleet-wide tag keep working.
Workflow-tool digests (Template C): the agent/job emits Markdown → a shared render step turns it into Template C's light-card HTML → that HTML is optionally archived somewhere durable → the sender delivers it as both the inline HTML body and a .html attachment, picking up Template A's footer automatically via the shared footer helper. Don't build a separate short Template A body for these — inline the full digest, unlike the Template B report flow.
Regardless of which body template: subject line and label conventions come from Template A, and the footer is always appended by a shared helper — it's not something you hand-write per template.
Sending it
These templates only produce HTML and text — something still has to call an email API. The
templates are deliberately transport-agnostic, so any of these works:
| Mechanism |
Use when |
Notes |
| A transactional email API (Resend, Postmark, SendGrid, SES…) |
Any new automation email |
The normal choice. All of them take an HTML body, a plain-text alternative, attachments, and a tag/label for filtering — which is exactly what these templates emit. Verify a sending domain first, or your mail lands in spam. |
| A mail MCP server |
Searching, labeling, or filtering existing mail |
Most read-only mail integrations can't send. Check before you wire one in as a delivery path. |
| Raw SMTP |
A one-off script, or a host that already has a relay |
Fine for low volume. You give up delivery tracking, bounce handling, and per-message tagging. |
[!IMPORTANT]
Write one sender and reuse it. The single biggest source of drift in an automation fleet
is every job growing its own send function — each with slightly different footer handling,
retry behavior, and API-key resolution. Put one small module behind the templates:
it takes (subject, html, text, attachments, tag), appends the shared footer, and is the
only thing in the codebase that holds the API key.
Key resolution is the trap. A sender that works interactively fails under a scheduler,
because the scheduled task runs as a different user and never sees your shell environment.
Decide explicitly where the key comes from — process env, a secrets file, the scheduler's own
secret store — and document it next to the sender, not in your head.
1---2name: html-email-templates3description: Three reusable HTML email templates for automated systems, built to survive the email clients that strip your CSS — a plain inline-styled notification/alert, a dark banner report wrapper, and an inline light-card digest — plus the subject-line, label and shared-footer conventions that keep a fleet of automated emails consistent and filterable. Transport-agnostic: the templates emit HTML and plain text, so any transactional email API (Resend, Postmark, SendGrid, SES) or SMTP relay can send them. Use whenever building or sending an automated email, alert, notification, digest, status report, or a generated report that gets emailed or attached.4---56# Email Template78Three templates, used together or alone. Pick based on what the email *is* and which system generates it:910| Email is... | Use | Delivery |11|---|---|---|12| A notification, alert, or short status update | **A — Notification** | inline HTML, the email body itself |13| A generated report (analysis, briefing, summary) from a script or job | **B — Report** | standalone `.html` file, attach it or link it — don't paste it as the email body |14| A markdown digest generated by a workflow tool (weekly/daily briefings, newsletters) | **C — Inline digest** | rendered to light-card HTML and inlined directly as the body (safe — inline styles only, no CSS vars) |1516A report email is usually **both A and B (or C)**: template A's footer is appended to every send regardless of which body template produced the content — that's a shared footer helper's job, not a per-template choice. For script/job artifacts, template B stays an attachment/link per the note below. For workflow-tool digests, the fleet can inline template C's *full* HTML as the body **and** still attach the archived `.html` file — both, not either/or.1718> **Why not inline Template B into the email body?** Template B relies on CSS custom properties (`var(--x)`), `background-clip: text`, and `backdrop-filter` — Gmail and Outlook strip or mangle all three. It renders correctly in a browser or PDF, not in an email client's DOM. Keep it as an attachment or a hosted link. Template C avoids this problem entirely by using only inline `style="..."` attributes — that's why it's safe to inline.1920---2122## Template A — Notification / alert2324Plain inline styles only, no `<style>` block, no CSS variables — this is what survives every email client.2526```html27<p style="margin:0 0 14px;font-size:15px;line-height:1.6;color:#1a1a1a;font-family:-apple-system,'Segoe UI',Helvetica,Arial,sans-serif">28 {body_paragraph_1}29</p>30<p style="margin:0 0 14px;font-size:15px;line-height:1.6;color:#1a1a1a;font-family:-apple-system,'Segoe UI',Helvetica,Arial,sans-serif">31 {body_paragraph_2}32</p>3334<!-- optional: one call-to-action link, omit the block if there is none -->35<p style="margin:20px 0 0;font-size:14px;line-height:1.5">36 <a href="{cta_url}" style="color:#0891b2;font-weight:600;text-decoration:none">{cta_label} →</a>37</p>3839<hr style="margin-top:24px;border:none;border-top:1px solid #ddd">40<p style="color:#888;font-size:12px;margin-top:8px">41 <strong>{sender_display_name}</strong> · automation fleet ·42 <a href="{home_url}" style="color:#888">Browse more →</a>43</p>44```4546Rules that make this the house style, not just *a* style:4748- **Paragraphs, not one wrapped `<p>`.** Multi-line text loses its breaks in a single tag — split on blank lines into separate `<p>`, single newlines become `<br>`.49- **One footer, every send.** `<strong>{sender_display_name}</strong> · automation fleet · <a>Browse more →</a>` — the bold name is the literal string a Gmail filter can match on, so pick one identity name per automation and never change it later.50- **`#0891b2` for links, `#888` for footer text.** Keep both — they're the only two non-body colors in the template.51- **Subject line:** `{Report/Alert Name} — {date or week range}`, e.g. `Weekly Security Briefing — Week of 2026-08-23 to 2026-08-29`, or `{Job Name} failed — {date}` for alerts. Tag every automation send with a label matching the job slug, if your mail provider supports labels.52- **Sender identity:** one `From` display name per automation family (e.g. `My Automation Fleet <alerts@example.com>`), reused across every email that family sends — don't invent a new sender per report.53- **Footer is provider/template-agnostic.** A shared footer helper (whatever sends your mail) should append this block after the body regardless of which template (A, B, or C) produced the content above it.5455## Template C — Inline digest5657For a workflow tool (n8n, Zapier, a scheduled script, etc.) that turns a fixed-structure Markdown briefing (`# title` / `**Week of:**` / `## sections` / `### per-story headings` / `- bullets` / `---` rules) into an inlined HTML email. One conversion function (`mdToHtml`) can be reused across every digest workflow, each supplying its own `accentColor`.5859```html60<!DOCTYPE html><html><body style="font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;max-width:720px;margin:0 auto;padding:24px;background:#fafaf9;color:#1f2937;">61<div style="background:#ffffff;padding:32px;border-radius:12px;border:1px solid #e5e7eb;">62 {body_html — h1/h2/h3/ul/p produced by mdToHtml(markdown)}63 <div style="margin-top:32px;padding-top:16px;border-top:1px solid #e5e7eb;color:#9ca3af;font-size:12px;">Generated by {tool} · {date} · Items considered: {n}</div>64</div>65</body></html>66```6768`mdToHtml` splits the Markdown on blank lines (`\n{2,}`) into blocks and converts each block by its **first line only**: `# `→`<h1>`, `## `→`<h2 style="color:{accentColor}...">`, `### `→`<h3>`, a block whose first line is `- `→`<ul><li>` (splits every `- ` line in the block into an `<li>`), anything else→`<p>` with `\n`→`<br>`. Links `[text](url)` and `**bold**` are regex-replaced after block conversion.6970**Two known bugs in this converter shape — worth fixing at the source, not just documenting:**7172- **Per-story detail lines don't listify.** If a prompt template puts `### {headline}` and its `- **Source:** ... / - **Link:** ... / - **What happened:** ...` bullets in the *same* block (no blank line between them), the block is classified by its first line and matches the `### ` branch — `.replace(/^### (.+)$/m, '<h3>...</h3>')` only replaces that first line, so the `- ` bullets underneath fall through unconverted: raw text with literal `- ` prefixes and no `<br>` between them (the `<br>` conversion only happens in the `else`/plain-paragraph branch). Fix: after the heading match, split the block's remaining lines and run them through the same `- ` → `<li>` logic, or handle "heading + bullets" as its own block type.73- **`---` rules render as literal text.** A lone `---` block doesn't match `# `, `## `, `### `, or `/^- /` (no space after the dashes), so it falls into the plain-`<p>` branch and renders as `<p>---</p>` instead of an `<hr>`. Fix: add a check for `/^-{3,}$/` before the final `else` and emit `<hr style="border:none;border-top:1px solid #e5e7eb;margin:20px 0;">`.7475Delivery can be two-stage rather than a single send: the workflow tool posts the inline HTML to your mail provider's API and separately archives the `.html` somewhere durable (a repo, a bucket); a downstream sender can then re-send the archived artifact as the actual email, with `--html` set to the same generated body and `--file` set to the archived copy as an attachment — appending Template A's footer at send time. That gets you **both** an inlined report body **and** an attached `.html` copy, not either/or.7677## Template B — Generated report7879Full standalone HTML document: dark card on a soft radial-gradient page, markdown-shaped content styling. Same skeleton for every report — the CSS never changes, only the four banner strings and the body content.8081```html82<!DOCTYPE html>83<html lang="en">84<head>85<meta charset="UTF-8">86<meta name="viewport" content="width=device-width, initial-scale=1.0">87<title>{Report Title}</title>88<style>8990:root {91 --bg-page: #07090f;92 --bg-wrapper: #0e1320;93 --bg-elevated: #131a2c;94 --bg-elevated-2: #182135;95 --bg-deep: #050810;96 --border: rgba(255,255,255,0.06);97 --border-strong: rgba(255,255,255,0.10);98 --text-primary: #e6edf7;99 --text-secondary: #b3bdd1;100 --text-muted: #7d899f;101 --accent: #7dd3fc;102 --accent-soft: #38bdf8;103 --accent-warm: #f5c97a;104 --gradient-banner: linear-gradient(135deg, #0a0f1f 0%, #14224a 40%, #1f3870 75%, #2a4f9c 100%);105}106107* { box-sizing: border-box; margin: 0; padding: 0; }108body {109 font-family: -apple-system, BlinkMacSystemFont, "Inter", "Helvetica Neue", Arial, sans-serif;110 background:111 radial-gradient(1200px 600px at 15% -10%, rgba(56,189,248,0.07), transparent 60%),112 radial-gradient(900px 500px at 110% 10%, rgba(125,211,252,0.05), transparent 60%),113 var(--bg-page);114 color: var(--text-primary);115 line-height: 1.6;116 font-size: 16px;117 padding: 28px 16px;118 -webkit-font-smoothing: antialiased;119 -moz-osx-font-smoothing: grayscale;120}121.wrapper {122 max-width: 880px;123 margin: 0 auto;124 background: var(--bg-wrapper);125 border: 1px solid var(--border);126 border-radius: 14px;127 overflow: hidden;128 box-shadow:129 0 1px 0 rgba(255,255,255,0.04) inset,130 0 30px 80px rgba(0,0,0,0.55),131 0 8px 24px rgba(0,0,0,0.35);132}133.banner {134 position: relative;135 background: var(--gradient-banner);136 color: var(--text-primary);137 padding: 44px 40px 32px;138 border-bottom: 1px solid var(--border-strong);139 overflow: hidden;140}141.banner::before {142 content: "";143 position: absolute;144 inset: 0;145 background:146 radial-gradient(600px 200px at 50% 0%, rgba(125,211,252,0.18), transparent 70%),147 radial-gradient(400px 150px at 85% 100%, rgba(245,201,122,0.07), transparent 70%);148 pointer-events: none;149}150.banner > * { position: relative; z-index: 1; }151.banner-eyebrow {152 font-size: 11px;153 font-weight: 600;154 letter-spacing: 2px;155 color: var(--accent);156 text-transform: uppercase;157 margin-bottom: 10px;158}159.banner-title {160 font-size: 28px;161 font-weight: 800;162 line-height: 1.22;163 margin-bottom: 8px;164 letter-spacing: -0.4px;165 background: linear-gradient(180deg, #ffffff 0%, #cfe1ff 100%);166 -webkit-background-clip: text;167 background-clip: text;168 -webkit-text-fill-color: transparent;169}170.banner-subtitle {171 font-size: 14px;172 color: var(--text-secondary);173 font-weight: 400;174}175.banner-date {176 display: inline-block;177 margin-top: 16px;178 background: rgba(255,255,255,0.06);179 border: 1px solid rgba(255,255,255,0.14);180 border-radius: 20px;181 padding: 5px 14px;182 font-size: 12px;183 color: var(--text-secondary);184 backdrop-filter: blur(6px);185}186.content {187 padding: 32px 40px 26px;188}189.content h2 {190 color: var(--text-primary);191 font-size: 20px;192 font-weight: 700;193 margin: 30px 0 14px;194 padding-bottom: 8px;195 border-bottom: 1px solid var(--border);196 letter-spacing: -0.2px;197}198.content h2:first-child { margin-top: 0; }199.content h3 {200 color: var(--accent);201 font-size: 15px;202 font-weight: 600;203 margin: 20px 0 8px;204 letter-spacing: 0.1px;205}206.content p { margin: 10px 0; color: var(--text-secondary); }207.content ul, .content ol { margin: 10px 0 10px 24px; color: var(--text-secondary); }208.content li { margin: 5px 0; }209.content li::marker { color: var(--accent); }210.content a {211 color: var(--accent);212 text-decoration: none;213 border-bottom: 1px solid rgba(125,211,252,0.3);214 transition: color 0.15s, border-color 0.15s;215}216.content a:hover { color: #bae6fd; border-bottom-color: var(--accent); }217.content code {218 background: var(--bg-deep);219 border: 1px solid var(--border);220 padding: 1px 6px;221 border-radius: 4px;222 font-family: "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;223 font-size: 0.88em;224 color: var(--accent-warm);225}226.content pre {227 background: var(--bg-deep);228 border: 1px solid var(--border);229 color: var(--text-secondary);230 padding: 14px 18px;231 border-radius: 8px;232 overflow-x: auto;233 margin: 14px 0;234 font-size: 13px;235}236.content pre code { background: none; border: none; padding: 0; color: inherit; }237.content blockquote {238 border-left: 3px solid var(--accent);239 background: var(--bg-elevated);240 padding: 10px 18px;241 margin: 14px 0;242 color: var(--text-secondary);243 font-style: italic;244 border-radius: 0 6px 6px 0;245}246.content hr { border: none; border-top: 1px solid var(--border); margin: 22px 0; }247.content table {248 border-collapse: separate;249 border-spacing: 0;250 width: 100%;251 margin: 16px 0;252 font-size: 14px;253 background: var(--bg-elevated);254 border: 1px solid var(--border);255 border-radius: 8px;256 overflow: hidden;257}258.content table th {259 background: linear-gradient(180deg, #1c2540 0%, #131a2c 100%);260 color: var(--text-primary);261 text-align: left;262 padding: 10px 12px;263 font-weight: 600;264 font-size: 12px;265 letter-spacing: 0.4px;266 text-transform: uppercase;267 border-bottom: 1px solid var(--border-strong);268}269.content table td { padding: 9px 12px; border-bottom: 1px solid var(--border); color: var(--text-secondary); }270.content table tr:last-child td { border-bottom: none; }271.content table tr:nth-child(even) td { background: rgba(255,255,255,0.015); }272.content table tr:hover td { background: rgba(125,211,252,0.04); }273.content strong { color: var(--text-primary); font-weight: 600; }274.content em { color: var(--text-secondary); }275.content img {276 display: block;277 max-width: 100%;278 height: auto;279 margin: 20px auto;280 background: #ffffff;281 padding: 12px;282 border-radius: 12px;283 box-shadow: 0 10px 30px rgba(0,0,0,0.45);284}285.footer {286 background: var(--bg-deep);287 border-top: 1px solid var(--border);288 padding: 18px 40px;289 font-size: 11px;290 color: var(--text-muted);291 text-align: center;292 letter-spacing: 0.5px;293}294@media (max-width: 640px) {295 body { padding: 0; }296 .wrapper { border-radius: 0; box-shadow: none; border-left: none; border-right: none; }297 .banner, .content, .footer { padding-left: 20px; padding-right: 20px; }298 .banner-title { font-size: 24px; }299 .content table { font-size: 12px; }300}301302</style>303</head>304<body>305 <div class="wrapper">306 <header class="banner">307 <div class="banner-eyebrow">{Eyebrow — e.g. the generating agent/product name}</div>308 <div class="banner-title">{Report Title}</div>309 <div class="banner-subtitle">{One-line subtitle}</div>310 <div class="banner-date">{Date or date range}</div>311 </header>312 <main class="content">313{report_body_html — h2/h3/p/ul/ol/table/blockquote/hr/img, exactly what a markdown-to-HTML render of the report produces}314 </main>315 <footer class="footer">{Footer disclaimer — e.g. "For Educational Purposes Only"}</footer>316 </div>317</body>318</html>319```320321Rules:322323- **Only four things change per report:** `<title>`, `banner-eyebrow`, `banner-title`, `banner-subtitle`, `banner-date`, and the `.content` body. The `<style>` block is copy-pasted verbatim — don't redesign it per report.324- **`.content` is markdown output, not custom markup.** Render the report body as plain `h2`/`h3`/`p`/`ul`/`ol`/`table`/`blockquote`/`hr`/`img` — the CSS already styles all of them. Don't hand-build stat cards or custom divs inside `.content`; the template doesn't have classes for them.325- **`.banner-eyebrow`** is the product/agent name generating the report; swap it for the automation's own name.326- **Footer is a one-line disclaimer or attribution**, not the fleet footer from Template A — this document stands alone (opened directly, attached, or hosted), it doesn't need the "Browse more →" link.327328---329330## Putting them together331332**Script/job reports (Template B):**3331. Generate the report as its own `.html` file using **Template B**, named for what it is (e.g. `Weekly_Security_Briefing.html`), saved next to the automation's other outputs.3342. Send the notification email using **Template A** as the body — one or two sentences on what's in the report, a CTA link if the report is hosted somewhere, or send the report file as an email attachment.3353. Keep the subject line and label conventions from Template A even when the body is otherwise custom — that's what makes an inbox filter and a fleet-wide tag keep working.336337**Workflow-tool digests (Template C):** the agent/job emits Markdown → a shared render step turns it into Template C's light-card HTML → that HTML is optionally archived somewhere durable → the sender delivers it as **both** the inline HTML body and a `.html` attachment, picking up Template A's footer automatically via the shared footer helper. Don't build a separate short Template A body for these — inline the full digest, unlike the Template B report flow.338339Regardless of which body template: subject line and label conventions come from Template A, and the footer is always appended by a shared helper — it's not something you hand-write per template.340341---342343## Sending it344345These templates only produce HTML and text — something still has to call an email API. The346templates are deliberately transport-agnostic, so any of these works:347348| Mechanism | Use when | Notes |349|---|---|---|350| **A transactional email API** (Resend, Postmark, SendGrid, SES…) | Any new automation email | The normal choice. All of them take an HTML body, a plain-text alternative, attachments, and a tag/label for filtering — which is exactly what these templates emit. Verify a sending domain first, or your mail lands in spam. |351| **A mail MCP server** | Searching, labeling, or filtering existing mail | Most read-only mail integrations can't send. Check before you wire one in as a delivery path. |352| **Raw SMTP** | A one-off script, or a host that already has a relay | Fine for low volume. You give up delivery tracking, bounce handling, and per-message tagging. |353354> [!IMPORTANT]355> **Write one sender and reuse it.** The single biggest source of drift in an automation fleet356> is every job growing its own send function — each with slightly different footer handling,357> retry behavior, and API-key resolution. Put one small module behind the templates:358> it takes `(subject, html, text, attachments, tag)`, appends the shared footer, and is the359> only thing in the codebase that holds the API key.360361**Key resolution is the trap.** A sender that works interactively fails under a scheduler,362because the scheduled task runs as a different user and never sees your shell environment.363Decide explicitly where the key comes from — process env, a secrets file, the scheduler's own364secret store — and document it next to the sender, not in your head.