in_app— the notification inbox you read via the notifications API. This is the default and always available.email— an email delivered through your own email provider (Resend in v1). Configured per project in the Console.
Content block implies intent
Each medium has its own content block on the send call, and including a block is how you say “attempt this medium”:payload⇒ the notification is written to the in-app inbox.email⇒ email is eligible for this send.
mediums array to set, and no fallback that derives an email from payload — if there is no email block, no email is sent.
payload and no in-app notification is created.
Email-only sends
On a direct send,payload is optional. A send carrying only an email block delivers the email and creates no in-app notification:
A send must carry at least one content block. Omitting both
payload and email returns a 400 — such a send names no medium, so it would do nothing. An explicit "payload": null counts as omitted.id back, and it carries the email delivery outcome for retrieval and shows up in the Console. It just isn’t a recipient-facing one: its status is not_requested, and it is excluded from the recipient’s feed, their unread count, and mark-all-read.
Because in-app was never requested, the recipient’s in-app preference is irrelevant to such a send. It is still gated by the email catalog, the recipient’s email preference, and a primary email contact, exactly like any other email.
payload remains required on a broadcast — see below.
Email on a broadcast
A broadcast may carry anemail block, and each matching recipient gets their own email. The per-medium gates below apply per recipient, so a broadcast reaches the intersection of its audience and the recipients whose email preference is on and who have a primary email contact.
Two rules are specific to broadcasts:
payloadis required. Email-only broadcasts are not supported, unlike email-only direct sends. A broadcast is the high blast-radius path, and “it also landed in their inbox” is what makes a mistaken send visible and recoverable afterwards. Without this rule, a fire-and-forget mass email would leave no in-product trace.- The email fan-out is capped per project, at 100 recipients by default. This is a safety rail, not a billing limit — raise it in your project’s email settings when you mean to.
Each medium is gated independently
Even when anemail block is present, email fires only when all of the following hold:
- Catalog — the project has declared the
(target, email)pair as a subscribable preference. - Preference — the recipient’s email preference for that target is enabled.
- Contact — the recipient has a primary email contact.
status and the email delivery outcome.
Unsubscribe
Every outbound email carries the one-clickList-Unsubscribe headers that Gmail and Yahoo require. Bodhveda hosts that page itself — when a recipient clicks it, Bodhveda flips that recipient’s email preference for that target off, exactly as if they had toggled it in your settings UI. Subsequent sends then record the email as muted.
That surface is automatic and there is nothing to call. The visible unsubscribe link in your email body is yours to build, and the same bulk-sender rules expect one. See Unsubscribe for both surfaces, and for what a spam complaint does.