Style your feed emails
Every email emit sends is built from a template. By default that's a clean, neutral shell — a heading, your feed's content, and a compliant footer. It's deliberately plain so it looks right for any feed. When you want your own look — a branded header, your colors, a layout tuned to what your feed actually contains — a feed can carry its own HTML template. This is how that works: the variables you get, the sandbox rules, and a full template you can copy.
Two ways an email gets styled
emit renders a feed's email in one of two paths. If the feed has no template, it goes through the default shell: a 640px column, system font, styled headings, code chips and lists, a light/dark-aware palette, and the CAN-SPAM footer. Every feed gets this for free, and it's good enough for most.
If a feed has a template_html set, emit uses that instead —
your HTML, rendered per email with a few variables filled in. One feed, one template; other
feeds are untouched. This is the escape hatch when the default isn't the look you want.
The five variables
A template is just HTML with placeholders. emit renders it and passes exactly these values — nothing else is available:
| {{ title }} | The email subject — a post title, or a digest line like "Dependency drift: 1 new post". Auto-escaped. |
|---|---|
| {{ body }} |
The post or digest content, already sanitized and safe. Inject it raw — write
{{ body }}, not {{ body | e }}. This is the one variable you
must include; everything else is chrome.
|
| {{ unsubscribe_url }} | A per-recipient one-click unsubscribe link. Legally required in the footer — always render it. |
| {{ postal_address }} |
Your CAN-SPAM postal address, if you've set one. May be empty — guard it with
{% if postal_address %}.
|
| {{ url }} | The single item's link, for per-post sends. Empty on digests, so don't depend on it for layout. |
{% if %},
{% for %}, filters — but there's no filesystem, no network, and no access to
anything beyond the five variables above. It's for shaping markup, not fetching data.
The mistake that makes styled content look unstyled
{{ body }} is your feed's content as HTML: paragraphs, links, nested lists, and
<code> spans. None of it carries inline styles. So the instinct — put
your CSS on the wrapper and call it done — leaves the actual content bare. A dependency
changelog rendered that way is a wall of unstyled bullet points with raw
<code> tokens running off the right edge.
The fix is a <style> block in the template <head> that
targets the content, not just the shell. Style code, ul,
li, a, and the headings — those rules reach the injected
{{ body }} because it lands inside your <body>. Gmail, Apple
Mail, and most modern clients honor an embedded <style>. For the few that
strip it (Outlook desktop), keep an inline style on the structural elements you control as a
fallback.
Two rules for code-heavy emails
Feeds that carry code — release notes, changelogs, API diffs — break in two specific ways unless you plan for them.
Long identifiers overflow. A token like
PaymentIntent.NextAction.DisplayBankTransferInstruction.FinancialAddress.supported_networks
is one unbroken string. In a fixed-width column it punches straight through the right
margin. Give every code chip
overflow-wrap: anywhere; word-break: break-word; so long dotted names wrap
instead of overflowing.
Dark mode inverts badly. A light template opened in a dark-mode client gets
auto-inverted by the client, and your carefully chosen chip colors turn to mud. Declare
<meta name="color-scheme" content="light dark"> and add a
@media (prefers-color-scheme: dark) block so you control both themes instead of
letting the client guess.
A full template: changelog style
Here's a complete template for a dependency-drift feed — a dark header band with a mono eyebrow, the content in a light card, styled code chips, dark-mode support, and the required footer. Copy it, change the colors, done.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="color-scheme" content="light dark">
<style>
body { margin:0; background:#f3f3f1; }
.wrap { max-width:660px; margin:0 auto; background:#fff;
font-family:-apple-system,"Segoe UI",Roboto,Arial,sans-serif; color:#1a1a1c; }
.head { background:#0d0d10; padding:26px 32px; }
.eyebrow { font-family:"SF Mono",Menlo,monospace; font-size:11px;
letter-spacing:.14em; text-transform:uppercase; color:#5eead4; margin:0 0 8px; }
.head h1 { font-size:20px; color:#fafafa; margin:0; }
.body { padding:28px 32px 8px; }
.body h2 a { color:#0f766e; text-decoration:none; }
.body a { color:#0d9488; }
.body li { font-size:14.5px; line-height:1.6; margin:0 0 7px; }
.body code { font-family:"SF Mono",Menlo,monospace; font-size:12.5px;
background:#eef2f1; color:#0f766e; padding:1.5px 5px; border-radius:4px;
border:1px solid #e0e7e4; overflow-wrap:anywhere; word-break:break-word; }
.foot { padding:24px 32px 32px; border-top:1px solid #ececec;
font-size:12px; color:#8b8b92; }
.foot a { color:#8b8b92; }
@media (prefers-color-scheme: dark) {
body { background:#050506 !important; }
.wrap { background:#0d0d10 !important; color:#d4d4d6 !important; }
.body h2 a, .body a { color:#5eead4 !important; }
.body code { background:rgba(94,234,212,.1) !important; color:#5eead4 !important;
border-color:rgba(94,234,212,.22) !important; }
}
</style>
</head>
<body>
<div class="wrap">
<div class="head">
<p class="eyebrow">Dependency drift</p>
<h1>{{ title }}</h1>
</div>
<div class="body">{{ body }}</div>
<div class="foot">
You subscribed to a dependency feed.
<a href="{{ unsubscribe_url }}">Unsubscribe</a>.
{% if postal_address %}<br>{{ postal_address }}{% endif %}
</div>
</div>
</body>
</html>
Setting it on a feed
A template is a field on the feed. Set it when you create the feed, or patch it onto an existing one — the value is the whole HTML document as a string:
curl -sX PATCH https://api.rssemit.com/v1/feeds/$FEED_ID \
-H "Authorization: Bearer $EMIT_KEY" \
-H "Content-Type: application/json" \
--data @- <<'JSON'
{ "template_html": "<!doctype html><html>...your template...</html>" }
JSON
Don't have the feed's id handy? GET /v1/feeds lists them. To pass a template
from a file cleanly, build the JSON with a tool that escapes it for you — for example
jq -Rs '{template_html: .}' template.html — and POST that.
Preview before you send
You don't have to guess. A feed's test send renders the real thing — your template, filled with the feed's latest item — and mails it to you:
curl -sX POST https://api.rssemit.com/v1/feeds/$FEED_ID/test \ -H "Authorization: Bearer $EMIT_KEY"
Open it on a phone and on desktop, in both light and dark mode. Check that long code tokens wrap, that links are legible on your header color, and that the footer still carries the unsubscribe link. When it looks right, the next real digest uses the same template automatically.