Skip to main content
Preferences let recipients control which notifications they receive. Preferences are set per target and can be:
  • Inherited: Using the project’s default setting.
  • Overridden: Explicitly set by the recipient.
Use recipient’s project preferences to build notification settings screens for your users.

Preferences are per medium

Each preference applies to a single medium. The in_app and email preferences for the same target are toggled independently, so a recipient can keep the in-app bell on while turning email off.
  • Project-level preferences form the catalog: they set the default for a (target, medium). Only in_app and email can be cataloged today.
  • Recipient-level preferences are the per-recipient opt-in/opt-out for a (target, medium).
When you set or check a preference, pass a medium (in_app or email). It defaults to in_app when omitted, so existing integrations keep working unchanged.

Strict targets

Strict targets is a per-project setting that turns the catalog into a gate: with it on, a send whose (target, medium) has no project-level preference is rejected with 400 and nothing is written.
It is off by default. With it off, sends to targets you have never cataloged are accepted, and the medium’s default decides what happens — in-app delivers, email does not.

Why off by default

Strictness is a maturity setting, not a correctness rule. On from the start, your very first targeted send fails with “create a project preference for it before sending” — at the moment you have built nothing and have no reason to know what a catalog is. That is a wall, not a guardrail. The order that works is the order you will naturally take:
  1. Send something untargeted. No setup at all.
  2. Add a target, because you want per-notification analytics.
  3. Discover preferences, because you want recipients to be able to mute it.
  4. Seed the catalog from your deploy, so it stops drifting from your code.
  5. Turn strict targets on, once your catalog is stable and you want a typo to fail loudly.

When to turn it on

Turn it on once your catalog is seeded from your deploy pipeline and you would rather a bad send fail than succeed quietly. What you get is the difference between a bug found on the first call and one found the week someone asks why they were never notified. The alternative — accepting the send and recording it as muted — hands you a 200 for a notification that will never reach a human, and makes “the recipient opted out” indistinguishable from “you typo’d the event name”. So Bodhveda rejects rather than muting. Leaving it off has a real cost worth naming: an uncataloged target has no preference surface. It appears on no settings screen, so a recipient cannot discover it, let alone switch it off. Off does not mean no consequences, it means the consequences are silent.
Your console’s project settings list the targets you have sent but not cataloged, so turning strict targets on is never a blind switch — you can see exactly what would start failing.

What the gate checks

The gate checks existence, not default_enabled. A cataloged entry with default_enabled: false is a real state — defined, currently switched off — so sending to it is accepted and simply reaches nobody. Only a missing entry is an error.
It applies per medium, to the mediums a send actually asks for. A send carrying only payload needs an in_app entry; one carrying only email needs an email entry; one carrying both needs both. It applies to direct sends and broadcasts alike. With strict targets off, a broadcast to an uncataloged target is accepted and reaches the recipients who explicitly subscribed to it, if any; the rest are reported as excluded_not_cataloged in the broadcast’s audience breakdown. A send with no target at all is never gated, whatever the setting — it claims no target, so there is nothing to check it against.

How a preference resolves

Whether a target delivers is resolved, not read from a single row. The first rule that matches wins:
  1. A mandatory catalog entry for the target (see below).
  2. The recipient’s rule for the exact target.
  3. The recipient’s topic: any rule for that channel/event.
  4. The project’s rule for the exact target (the catalog).
  5. The project’s topic: any rule.
  6. The medium’s default — in-app delivers, every other medium does not.
A topic: none target never takes an any rule — none means “this rule has no topic”. The preference read reports state.cataloged and state.mandatory separately from state.enabled. Use them to decide what to render on a settings screen; use enabled to know what will actually happen.

Wildcards satisfy the gate

A topic: any catalog entry matches every concrete topic under that channel and event — for the gate exactly as for resolution. One entry covers an unbounded set of runtime-generated topics. This is what makes per-resource muting practical. Catalog comments/any/reply once, then send comments/post_812/reply, comments/post_913/reply, and so on. Every send passes the gate, and a recipient rule on one exact target mutes that one thread and nothing else — no state on your side.
Do not conclude that a per-resource target needs one catalog entry per resource id. It never does, and a catalog with a row per post is one that cannot be seeded from your code. One wildcard entry is the whole answer.

Mandatory entries

Some notifications must not be optional: password resets, security alerts, billing failures, a one-shot welcome. But everything in the catalog is normally something a recipient can switch off. Set mandatory: true on a catalog entry to resolve that. The entry exists, so sends pass the gate; the recipient’s toggle is refused with a 400, and any rule they already had is ignored. Mandatory is independent of strict targets. It is a property of the catalog entry, not of the gate, so it behaves identically whether or not strict targets is on: the entry is what makes the target non-negotiable, and cataloging it is what you had to do anyway to give recipients a switch you then refuse to honour.
Render mandatory entries as locked, not as a switch. A toggle that saves and changes nothing is worse than one that refuses — the recipient walks away believing they opted out.
default_enabled still applies to a mandatory entry, so setting it false stops the notification. Mandatory removes the recipient’s choice, not yours.

Seed the catalog from your deploy

Your desired catalog lives in your code; the real one lives in Bodhveda. They drift the moment you ship a new event and forget to update Bodhveda to match. Derive the catalog from whatever already defines your events, and push it with upsert preferences as a step in your deploy pipeline rather than a one-time setup task. It is an idempotent merge, so a deploy that changes nothing is a no-op. This is worth doing before you turn strict targets on, and it is worth doing even if you never do. With the gate off, drift is silent: the notification sends, but the target is on no settings screen, so no recipient can mute it.
Once strict targets is on, seed before the new code starts serving, and let a failed seed fail the deploy. Ship code that sends a target you have not cataloged yet and every one of those notifications returns 400. Stopping the deploy leaves your previous version running, which only sends targets that are already cataloged.
Run it on every deploy, not just the deploy that adds an event. A catalog seeded by hand is a catalog that falls behind the code that depends on it. If you want to build something like Reddit where an author is subscribed to their own post and any other user can explictly follow someone else’s post as well.
  • For the author, you can send a direct notification and it will always deliver unless the recipient explicity unsubscribe to that target. You should still use target so that you get analytics based on targets.
  • For the user who wants to follow someone else’s post, you should subscribe the recipient to that target and if they unfollow the post, unsubscribe them from that target.
  • You could subscribe the author to their post’s target so that whenever some event is triggered on that post, you can just send a broadcast notification and the author and the other user both will recieve the notification.