Skip to main content

Privacy friendly

Notifizz does not store your users’ personal data. Names, plans, segments, custom fields, profile graphs — none of it lives in the platform. When a campaign needs a recipient’s profile, the orchestrator fetches it live from your systems via an enricher, uses it to send the notification, and lets it go. The only customer data Notifizz retains is the strict minimum required to deliver the message and honour later requests (unsubscribe, right to erasure). That single rule — fetch live, don’t copy — is the core of the architecture. Everything below is what falls out of it: the email hashing story, the bounded cache, the retention curseurs. None of it is a configuration option you opt into; it’s how the platform is wired.

TL;DR

  • No warehouse sync. Customer profiles stay in your systems. The orchestrator fetches what it needs at notification time via enrichers — never copies it.
  • Email is hashed by default. Plain email is kept encrypted only inside an operational window (default 14 days, configurable). After that, only an org-salted SHA-256 hash remains, sufficient for de-duplication and the suppression list.
  • The enricher cache is bounded, not a sync. Per (org, env, enricher, params) keying with a TTL declared at registration. Eviction is automatic; only recently used records exist.
  • Retention is configured per organisation, with conservative GDPR-aligned defaults and platform-enforced minima.

Why it matters

GDPR Article 5 — and the equivalent regimes everywhere else — require you to hold customer data only as long as you need it, for explicit purposes. Notification platforms in the Customer.io / Braze / Iterable lineage centralise all customer data in their own database, indefinitely, in case a campaign ever fires for that customer. Your DPO ends up auditing a parallel customer graph that exists only for “we might send something one day”. Notifizz’s design eliminates that parallel graph. The privacy surface you have to audit is the event payloads you control, plus the per-message delivery snapshots — not a copy of every customer.

The three pillars

1. Enrichers — no warehouse copy

The platform doesn’t sync your customer database. Instead, the orchestrator calls enrichers — server-side functions you register that fetch live data from your systems at notification time.
client.enricher("fetchUser", {
  input: z.object({ userId: z.string() }),
  output: z.object({ id: z.string(), email: z.string(), plan: z.string() }),
  cache: { ttl: "1h" },
  handler: async ({ userId }) => {
    const user = await db.users.findOne({ id: userId });
    return { id: user.id, email: user.email, plan: user.plan };
  },
});
  • Privacy surface stays in your systems. Notifizz processes; you store.
  • No drift. The user’s email is whatever your DB returns at notification time, not what last night’s sync ran with.
  • No schema migration. New attribute? Add a field to the enricher’s output. Renamed? One file changes.
The full positioning case is in why enrichers; the wire-level protocol is in enrichers protocol.

2. Email hashing — plain text only when needed

The platform handles email in two distinct states: plain (encrypted at rest) during the operational window, hashed only outside it. Suppression lists store hashes only, so an unsubscribe survives forever without keeping the address forever.

Normalisation, then hash

Every incoming email is normalised before hashing:
normalize(email) = lowercase(trim(email))
Optional Gmail normalisation (drop dots and +tag in the local-part) is opt-in per organisation — it improves de-duplication but risks merging addresses the user intentionally separated. Off by default. The hash uses an organisation-specific salt stored in a KMS:
email_hash = SHA-256(organisation_salt || normalize(email))
Per-org salts prevent cross-organisation correlation if a hash ever leaks; per-email salts are deliberately not used because they would break de-duplication.

Plain email lifecycle

The plain (encrypted) email is held as long as a notification within the operational window references it — and purged automatically otherwise. When a new send arrives later for the same address, the hash matches the existing recipient, the plain email is reintroduced, and the history is continuous.
StateWhat’s stored
Active sending windowPlain email (encrypted, AES-GCM, per-org KMS key) + hash + delivery history
Outside the windowHash only + delivery history (pseudonymised)
Suppression list entryHash only (no plain text — ever)
The window defaults to 14 days (covers Postmark’s bounce / complaint feedback loop) and is configurable per organisation. The platform enforces a 7-day minimum below which bounce handling breaks.

3. Cache — bounded, not a sync

Enricher responses are cached server-side to keep latency low. This is not a sync. The differences are structural:
Sync (warehouse-sync platforms)Cache (Notifizz enrichers)
Pre-fetches data before any notification firesFetches only when a notification needs it
Stores every customer in the platform’s DBStores only the records used recently
Holds data indefinitelyEvicts on TTL or invalidation
Privacy surface = full customer databasePrivacy surface = records used in the last TTL window
Cache key is (organisationId, environmentId, enricherName, sha256(params)) — strict cross-tenant isolation, identical params from different organisations never collide. TTL precedence: per-call override → declared at registration → 1-hour default. An enricher declaring cache: false cannot be overridden upward. is where the keying lives; enrichers protocol covers the policy in detail.

What Notifizz stores — and what it doesn’t

DataStored?Lifecycle
Your external_id (your own user id)YesLives until you declare deletion
Plain emailEncrypted at restHeld within the operational window (default 14 days), purged after
Email hash (org-salted SHA-256)YesLives with the recipient or in the suppression list
Notification delivery events (sent, opened, clicked)YesLive with the recipient; pseudonymised once plain email is purged
User notification inboxYesLives with the recipient
Customer traits (name, plan, segment, custom fields)NoFetched live via enrichers, never persisted
Profile graph beyond identifiersNoStays in your systems
The cap is deliberate. You can’t ask Notifizz to “store the user’s plan” or “store the user’s segment”. The platform doesn’t have those columns. If a campaign needs them, an enricher fetches them on demand.

Retention configuration

GDPR places retention under the responsible-party (you, the controller) — not the processor (Notifizz). Notifizz exposes configurable curseurs per organisation, with conservative defaults and platform-enforced minima:
CurseurDefaultMinimumNotes
email_retention_days147Below 7 days, Postmark bounce handling breaks.
analytics_retention_daysunlimited (lives with recipient)30Below 30 days, dashboards lose value.
suppression_list_retention_years33 (max 10)Legal floor — Article 21 + ePrivacy obligation to honour past unsubscribes.
The current configuration is the truth. A daily retention job re-applies the live curseurs to existing data; lowering a curseur purges records that fall outside the new window. Lowering a curseur surfaces a confirmation dialog showing the number of recipients impacted before applying — destructive changes are deliberate, not accidental. All curseur changes are logged in the audit trail (actor, timestamp, old → new, impacted count) — useful for CNIL / ICO inspections and for protecting both parties in a dispute.

Suppression and right to be forgotten

When a user unsubscribes, marks a message as spam, or asks to be deleted, the obligation is “never email again” plus (for deletion) “remove the record”. Notifizz handles both without keeping the address indefinitely:
  • Unsubscribe / spam complaint → the email is hashed, added to the per-org suppression list. Subsequent sends hash the incoming address and check the list before dispatch. The plain email is not retained.
  • Right to erasure (DELETE /v1/subjects/:external_id) → declared by your system as part of your own user-deletion flow. Notifizz purges the recipient, all delivery events, and the inbox immediately.
  • Inactive cookie subjectsWebSubject records (anonymous widget visitors) without activity for 90 days are purged automatically. Anonymous consent is ephemeral by design.
A typical wiring on your side:
async deleteUser(userId: string) {
  await this.db.users.delete(userId);
  await notifizz.deleteSubject(userId);
}

How this composes with the rest

  • Enrichers — the no-copy primitive, the largest contribution to the minimisation story.
  • Recipients — recipients carry only id + email (plus arbitrary fields you control); never absorbed into a profile store.
  • Authentication — widget auth modes keep authenticated and anonymous traffic strictly separated, in line with the CNIL January 2026 recommendation.
  • Activity log — retention curseur changes appear in the audit trail with full context.

FAQ

The dashboard fetches the live profile from a configurable per-organisation Profile Resolver webhook at render time — same model as enrichers, scoped to UI display. Names, avatars, CRM links, custom fields stay in your systems; Notifizz never persists them. If the resolver is unconfigured or unavailable, the UI falls back to the email or the truncated external_id — display degrades, the platform doesn’t block.
Because a user who unsubscribes today must not be re-emailed by a fresh campaign next year. The hash lets the platform recognise “this address has unsubscribed” without keeping the address itself. It also handles the case where the same person comes back through a different ingest path — the hash matches, the suppression applies, no email goes out.
A global salt would make a hash-table leak correlatable across every Notifizz customer at once. A per-org salt confines the blast radius to one organisation. Per-email salts would be even safer, but they’d break the de-duplication that the hash exists to enable.
No. A cache stores records actually used in the last TTL window. A sync stores every customer indefinitely. The privacy footprint scales with notification volume, not with customer count, and evicts itself automatically. If you need stronger guarantees for a specific enricher (say, one that fetches sensitive fields), declare cache: false at registration — the orchestrator will hit your endpoint on every call.
During the campaign window, yes — the plain email is held encrypted to dispatch. Once the operational window expires, plain emails are purged; only hashes remain. The window is configured per organisation; for marketing-heavy use cases you can extend it, with the trade-off documented in your retention policy.
Yes — the API exposes a structured export per recipient (delivery history, inbox, hashed and plain identifiers as applicable), suitable for satisfying a Right of Access or Right to Portability request.
Notifizz is the processor in the GDPR sense; you remain the controller. The retention curseurs are exposed precisely because the duration is your decision. Notifizz enforces minima (so a controller can’t ask the platform to break Postmark or skip the legal suppression-list floor) and provides safe defaults, but the policy choice belongs to you. A model DPA is provided at onboarding.

See also

Why enrichers

The case against warehouse-sync platforms.

Enrichers protocol

Cache policy, HMAC, anti-replay — the wire spec.

Recipients

The minimal recipient shape and why it stays minimal.

Authentication

Auth modes that keep the authenticated / anonymous boundary strict.