Retention, export & erasure

Your users’ data is your obligation, and it is sitting on our disks. This page is the three things you need to be able to do about that.

You need toWhere
Know how long we keep thingsRetentionUsage in the console
Answer “send me my data”Export — a button, or nb.users.export()
Answer “delete my data”Erasure — a button, or nb.users.erase()
Take your whole audience elsewhereAudience exportSubscriptions → Export everyone

How long we keep the delivery log

Every send writes one row per recipient per channel: who, when, on what, and whether it landed. That is the log behind the report screens, and it is the largest thing we store about your users. It is kept for a window that comes with your plan:

PlanDelivery history
Free30 days
Pro90 days
Scale180 days
Business365 days

Older entries are removed. Campaign totals are not part of that window and are never pruned — a report from two years ago still says how many people it reached, how many failed and what the click rate was. What it loses is the row-by-row list underneath. Your monthly usage figures are stored separately too, so nothing that decides an invoice depends on a log that expires.

The console shows your own number on Usage, and an activity list that is empty because of it says so rather than showing you a campaign that appears to have reached nobody. Those are opposite facts and they should not look the same.

Two floors are applied under whatever your plan says, because a shorter window would break something quietly:

Custom events you send with track() are not pruned: they feed attribution revenue, which is a lifetime number for your business rather than a delivery record.

Everything about one person

Someone asks your support team for a copy of their data. This is the whole of it — profile, devices, addresses, inbox, events, delivery history and opt-outs — as one JSON file, from the person’s page in Subscriptions, or from your own backend:

const data = await nb.users.export("user-42");
// { user, devices, channel_identities, inbox, events,
//   deliveries, suppressions, preferences, exported_at, note }
GET /v1/users/user-42/export            // server key
GET /v1/orgs/:orgId/apps/:appId/users/:userId/export   // console session

Delivery history and events are capped at the most recent 1,000 of each and are subject to the retention window above — the file says so in its own note field, because a file like this usually gets forwarded to the person it describes and “is this all of it?” is the first question they ask.

Forgetting somebody

Different from an unsubscribe, and worth being precise about: unsubscribing is somebody who said stop, and they stay in your audience so you can prove they opted out. Erasure is somebody who said forget.

await nb.users.erase("user-42");
// { erased: true, deleted: {...}, anonymised: {...}, tombstoned: 2 }
DELETE /v1/users/user-42                                  // server key
DELETE /v1/orgs/:orgId/apps/:appId/users/:userId           // console, with a typed confirm

Three things happen, and the difference between them is the whole design:

WhatHappens to itWhy
Profile, addresses, inbox, preferences, queued automationsDeletedThese exist only to describe one person.
Delivery events, custom events, attributionsKept, anonymisedEach of these is also a count. A campaign that reached 4,000 people must still say 4,000 tomorrow, whoever asked to be forgotten in between. The address, the raw provider response and the link to the person come off; the row stays and names nobody.
DevicesKept, unlinkedA push token belongs to a browser or a handset, not to a person, and the same phone can be handed on. The device stops being theirs.

The part that looks wrong and is not

We record that the addresses must never come back — as a salted one-way hash, never the address itself. A later import of an older CSV hashes each row and refuses any that match, with the reason on the rejected row.

Without it, an erasure lasts exactly as long as it takes to import the export sitting on somebody’s laptop from last week — which is the single most likely thing to happen next. Keeping a hash nobody can reverse is how the request is honoured, not how it is defied.

There is no undo. The console asks you to type the person’s own external id, email address or phone number, whichever you have in front of you, for the same reason deleting an app asks for the app’s name.

Erasure is idempotent: erasing somebody already erased returns erased: false rather than an error, so a “delete my account” flow in your own product can retry safely.

Taking your whole audience with you

Subscriptions → Export everyone (CSV), or:

GET /v1/orgs/:orgId/apps/:appId/audience/export

One row per person, not per device — somebody with three phones is one row. It carries every reserved attribute, your own properties as JSON, device counts, and whether they have opted out. There is no lock-in here and it is not a favour: it is your audience, and the same file shape our import reads.

What we do not offer, and what to do instead

There is no self-serve “delete my whole organization” button. Deleting an app from Settings removes its audience, messages, delivery events, keys and everything else belonging to it. For an entire account, email [email protected] from the owner’s address — it is a rarely-used, entirely irreversible action and it is worth a human on both ends.

← User attributesSecurity & keys →