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
| Group | Tags |
|---|---|
| Text | p, br, span, strong/b, em/i, u, s/strike/del, ins, sub, sup, mark, small, code |
| Headings | h1-h6 |
| Lists | ul, ol, li |
| Structure | div, section, header, footer, figure, figcaption, blockquote, hr |
| Tables | table, thead, tbody, tfoot, tr, th, td, caption, colgroup, col |
| Links & images | a, 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.
| Category | Properties |
|---|---|
| Colour | color, background-color, opacity |
| Spacing | padding (+ -top/-right/-bottom/-left), margin (+ sides) |
| Border | border (+ sides), border-color, border-width, border-style, border-radius |
| Size | width, max-width, min-width, height, box-sizing |
| Text | text-align, text-decoration, text-transform, vertical-align, font-weight, font-size, font-style, font-family, line-height, letter-spacing |
| Layout | display, 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.
| Class | On | What it does |
|---|---|---|
kp-panel | section | A soft card: a bordered, padded block (a footer, an aside). |
kp-panel kp-panel--brand | section | The branded hero: a full-bleed, edge-to-edge coloured card, the "envelope" at the top of the letter. |
kp-lede | p | The larger intro line under a heading. |
kp-button | a | Styles a link as a call-to-action button. Still a link: it degrades to underlined text anywhere the styling is absent. |
kp-muted | p | Small print (contact details, fine print). |
kp-contact | ul | A plain, un-bulleted list for contact rows. |
ka-align-center / ka-align-right / ka-align-justify | any block | Text 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 and images
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, objectsjavascript:/vbscript:/ non-imagedata:URLsposition: fixed/stickyand 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.
Send content
Deliver digital mail to a recipient, from letters and payslips to invoices and statements. Covers the envelope, structured attributes, multi-part bodies, retention, and the delivered-vs-retained outcome.
Content types in depth
The content types the send endpoint accepts, their stable vs preview status, and the structured attributes each carries. Grounded in the Nigerian market.