GUIDE · JULY 17, 2026 · 6 MIN

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.
Templates render in a sandbox. Standard Jinja works — {% 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.

Prefer not to hand-write HTML? The default shell already styles headings, code, and lists and adapts to dark mode — every feed gets it with no template at all. Custom templates are for when you want a specific brand, not a prerequisite for a good-looking email.
Open the dashboard → Copy-paste templates