Keepable docs
Sender API

HTML and CSS

What a text/html mailpiece body may contain. The allowed tags, the inline-CSS property list, the kp-* branded blocks, and everything the sanitiser strips, so your branded mail renders the way you intend.

A text/html body lets you brand a mailpiece: your own colours, a hero card, a call-to-action button. To keep every recipient safe, Keepable sanitises the HTML on write: it stores only markup on the allowlist below, and strips the rest. Nothing you send can run a script, load a remote asset, track an open, or break out of its frame.

This page is the reference for exactly what survives. Send anything; only the allowlisted parts are kept. There are no surprises at delivery if you stay within it.

Prefer not to hand-write HTML? The portal composer builds all of this for you (including the branded blocks below) and shows a live preview of what the recipient will see. This page is for senders authoring HTML directly via the API or an upload.

Two ways to brand

There are two levels, and you can mix them:

  • The kp-* blocks (recommended): a small vocabulary of ready-made classes for panels, buttons, and text styles. They carry a light and dark variant, so they stay legible whichever theme the recipient uses. This is the safest way to get the branded "envelope" look.
  • Your own inline styles: for full control, set style="…" on any element, from an allowlisted set of CSS properties. A colour you set is fixed, so it will not adapt to dark mode (see Dark mode).

Allowed elements

GroupTags
Textp, br, span, strong/b, em/i, u, s/strike/del, ins, sub, sup, mark, small, code
Headingsh1-h6
Listsul, ol, li
Structurediv, section, header, footer, figure, figcaption, blockquote, hr
Tablestable, thead, tbody, tfoot, tr, th, td, caption, colgroup, col
Links & imagesa, img

Any tag not listed is unwrapped (its text is kept, the tag dropped). class is allowed on any element; your own class names are kept, though they only take effect for the kp-* vocabulary; Keepable does not accept sender <style> blocks, so a class of your own has no stylesheet to bind to.

Inline styles

Set style="…" on any element. Each declaration is validated against the property list below, and its value is checked (a colour must parse as a colour, a length as a length). A declaration that names a property outside this list, or carries an unsupported value, is dropped; the rest of the style is kept.

CategoryProperties
Colourcolor, background-color, opacity
Spacingpadding (+ -top/-right/-bottom/-left), margin (+ sides)
Borderborder (+ sides), border-color, border-width, border-style, border-radius
Sizewidth, max-width, min-width, height, box-sizing
Texttext-align, text-decoration, text-transform, vertical-align, font-weight, font-size, font-style, font-family, line-height, letter-spacing
Layoutdisplay, flex, flex-direction, flex-wrap, justify-content, align-items, align-self, float, list-style-type

There is no url() property here on purpose: not background-image, not list-style-image, not cursor. That is what makes styles safe: a declaration can never point at a URL, so it can never load a remote asset or become a tracking pixel. position is also excluded, so content cannot escape the reading flow.

The kp-* branded blocks

These classes are pre-styled by Keepable and adapt to light and dark mode. Use them for the branded look without setting a single colour yourself.

ClassOnWhat it does
kp-panelsectionA soft card: a bordered, padded block (a footer, an aside).
kp-panel kp-panel--brandsectionThe branded hero: a full-bleed, edge-to-edge coloured card, the "envelope" at the top of the letter.
kp-ledepThe larger intro line under a heading.
kp-buttonaStyles a link as a call-to-action button. Still a link: it degrades to underlined text anywhere the styling is absent.
kp-mutedpSmall print (contact details, fine print).
kp-contactulA plain, un-bulleted list for contact rows.
ka-align-center / ka-align-right / ka-align-justifyany blockText alignment.
<section class="kp-panel kp-panel--brand">
  <h1>Your April statement is ready</h1>
  <p class="kp-lede">Hi Ada, your account summary for April is ready to view.</p>
  <p>Everything you were sent is sealed and yours to keep.</p>
  <a class="kp-button" href="https://app.example.com/statement">View your statement</a>
</section>

Links may use http, https, mailto, tel, or cid. Every link is given target="_blank" and rel="noopener nofollow"; javascript: and other schemes are removed.

Images are referenced by a content-scoped cid:<id> and supplied as top-level assets, so they load same-origin from the recipient app, never from a host you control. Remote <img src="https://…"> and data: image URLs in the body are not kept; this is the tracking-pixel vector the design avoids.

Dark mode

Recipients can read in light or dark mode. The kp-* blocks flip automatically. A colour you set with an inline style does not: a dark teal you choose for text stays dark teal, which may be unreadable on a dark background.

If your brand look must hold in both themes, build it from the kp-* blocks. If you set your own colours, set the background and the text together on the same element (as in a fully-coloured panel) so you control the contrast; don't rely on the recipient's theme behind your text.

Always removed

Regardless of the above, these are stripped every time; they are the reason the sanitiser exists:

  • <script>, event handlers (onclick, onerror, …), and any executable content
  • <style> blocks and external stylesheets (<link rel="stylesheet">)
  • url() in CSS, and every remote asset: images, fonts, iframes, objects
  • javascript: / vbscript: / non-image data: URLs
  • position: fixed / sticky and other layout-escape tricks
  • Anything that would let a sender see when a recipient opens their mail

A full example

A complete branded letter (a hero, body, a CTA, and a contact footer) using only the allowlist:

<section class="kp-panel kp-panel--brand">
  <h1>Your orange envelope 2026</h1>
  <p class="kp-lede">Sign in to open your annual pension summary.</p>
  <ul>
    <li>Your pension forecast</li>
    <li>What you earned toward your pension this year</li>
    <li>Tips that affect your future pension</li>
  </ul>
  <a class="kp-button" href="https://app.example.com/open">Open your envelope</a>
</section>

<p>You can read your statement any time from your Keepable inbox.</p>

<section class="kp-panel">
  <h3>Questions?</h3>
  <p class="kp-muted">We are glad to help, any time.</p>
  <ul class="kp-contact">
    <li><a href="tel:+2340000000000">+234 000 000 0000</a></li>
    <li><a href="mailto:support@example.com">support@example.com</a></li>
  </ul>
</section>

Send it as a text/html part; the recipient sees the branded card in both light and dark mode.

If your branded HTML is the full rendering of a document you also attach as a PDF, send the HTML as an alternative with role: "document". The reader then leads with your HTML and minimises the sealed PDF to a preview card, instead of treating the HTML as a short note above the PDF.