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.
Every content delivery names a content_type. It
does two things: it tells the recipient app how to frame the item (icon,
grouping, the by-type filter), and it selects which attributes schema applies.
The envelope is identical for every type. content_type is not a
discriminator that changes the required envelope fields; it selects the
optional attributes shape. Adding a new content type is an additive change;
it never disturbs the envelope or existing types.
Stable vs preview
The vocabulary is split by release status:
| Status | Types |
|---|---|
| Stable | letter, payslip, invoice, statement |
| Preview | credit_note, booking, receipt, form, survey_invitation, campaign, registered_letter |
- Stable types are production-ready; their semantics and
attributesare covered by the deprecation policy. - Preview types are visible and usable, but their semantics and
attributesshape may still change. Don't depend on them in production yet.
The status is also machine-readable in the spec as x-keepable-status, so
tooling can surface it.
An unknown content_type is rejected with
422. The recipient's
by-type filter relies on the value, so the set is closed and validated.
Why these are the stable four
The stable set is the highest-frequency, identity-anchored mail in the Nigerian market:
payslip: issuing payslips is a statutory payroll record (the Labour Act, the Personal Income Tax Act, the Pension Reform Act), so every formal employer produces them monthly.invoice: FIRS National E-Invoicing is mandatory for large taxpayers from August 2025 and extends to smaller VAT-registered businesses through 2026. A cleared e-invoice can travel to the inbox carrying its clearance reference (irn).statement: monthly bank statements are a customer right, and a signed, identity-anchored channel is a direct answer to the "fake credit alert" fraud the CBN repeatedly warns about. Pension (RSA) statements fit here too.letter: the generic baseline for anything without a more specific type.
Attributes by type
attributes carries the structured, machine-readable fields the recipient app
renders into a rich view. Four types have a typed schema today; every other type
accepts a free-form attributes object.
Put anything the recipient must see or act on (an amount, a due date, a
location) in attributes or a part,
never in metadata, which Keepable never reads or renders.
payslip (stable)
| Field | Required | Notes |
|---|---|---|
pay_period | yes | The period covered, e.g. "2026-03" (YYYY-MM). |
net_pay | yes | Take-home pay as a decimal string. Never a float. |
gross_pay | no | Gross pay as a decimal string. |
currency | yes | ISO 4217 code, e.g. NGN. |
{ "pay_period": "2026-03", "gross_pay": "320000.00", "net_pay": "250000.00", "currency": "NGN" }invoice (stable)
| Field | Required | Notes |
|---|---|---|
amount | yes | Total due as a decimal string. |
currency | yes | ISO 4217 code. |
due_date | yes | ISO 8601 date (YYYY-MM-DD). |
invoice_number | no | Your invoice reference. |
irn | no | FIRS Invoice Reference Number from e-invoicing clearance, where applicable. |
{ "amount": "125000.00", "currency": "NGN", "due_date": "2026-06-30", "invoice_number": "INV-88213", "irn": "IRN-7F3A9C20-2026" }statement (stable)
| Field | Required | Notes |
|---|---|---|
period_start | yes | ISO 8601 date. |
period_end | yes | ISO 8601 date. |
currency | yes | ISO 4217 code. |
opening_balance | no | Decimal string. |
closing_balance | no | Decimal string. |
account_reference | no | The account or RSA reference the statement covers. |
{ "period_start": "2026-03-01", "period_end": "2026-03-31", "currency": "NGN", "opening_balance": "10000.00", "closing_balance": "42500.50" }booking (preview)
| Field | Required | Notes |
|---|---|---|
starts_at | yes | RFC 3339 date-time. |
location | yes | Where the appointment is. |
ends_at | no | RFC 3339 date-time. |
reference | no | Your booking reference. |
{ "starts_at": "2026-04-02T10:30:00Z", "ends_at": "2026-04-02T11:00:00Z", "location": "Lagos Island branch", "reference": "BK-4471" }Money, dates, currencies
Across every typed schema: money is a decimal string ("125000.00", never a
float), dates are ISO 8601 (YYYY-MM-DD), date-times are RFC 3339, and
currencies are ISO 4217 codes (NGN). Violations are rejected 422.
Adding a content type
New types graduate from preview to stable as their semantics settle, and new
preview types appear as the market calls for them (receipt and
registered_letter are reserved this way as forward-looking preview types).
Because a type is just a vocabulary entry plus an optional attributes schema,
adding one never changes the envelope or any existing type, so your integration
keeps working untouched.
When you need a signature
If the recipient must sign rather than just read, that is not a content type at all: use an agreement, which returns a signed, tamper-evident covenant.
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.
Agreements
Run e-signature workflows. Send a document to one or more signers, track signatures as they land, and download a tamper-evident PAdES-LTV covenant when everyone has signed.