Inbox rules
An inbox rule describes something worth telling people about: what triggers it, who receives it, and what it says. nullplatform ships built-in rules for approvals, deployments, builds, and more. Create your own rules to announce events nullplatform doesn't observe, such as a security finding from your CI scanner or a budget alert from your cost tool.
Every rule also shows up in each person's inbox settings, so people can tune or mute your rules like the built-in ones.
Manage inbox rules from the UI
Go to Platform Settings > Communications > Inbox rules. The list shows the built-in rules and your organization's own, with what triggers each one and where it applies. Use the Rule filter to show only Built-in or Custom rules.
Walk through the whole flow, from the list to sending a rule from your pipeline:
Built-in rules
From a built-in rule's actions menu, you can:
- Open it and edit it for your organization. An edited rule shows an Edited chip, and Reset to default discards your changes.
- Deactivate it, so nobody in your organization receives it until you activate it again.
- Duplicate it to start a new rule from it. A built-in rule keeps its ID and source, so this is how you get a different trigger.
Create your own rule
Click New inbox rule to open the editor. It validates the YAML on the left as you type, and Overview, next to it, explains in plain words when the rule fires and who it notifies, with a preview of the notification.
Pick one of the Examples instead of starting from scratch: custom events that your workflows or CI send, or platform events that fire on what happens in nullplatform.
Once the rule is saved, its Emission tab shows a ready-to-run curl command, and you can Send test to me to see it in your own inbox.
To also post the rule to Slack, add a channel under Also post to, at the end of Overview. See Post inbox rules to Slack.
Without permission to manage inbox rules on a rule's resource, the editor opens in view-only mode.
Deactivate stops a rule right away and frees one of your slots, and you can activate it again later. Notifications already sent stay in people's inboxes.
Delete removes the rule and everyone's inbox settings for it. To stop a rule for a while, deactivate it instead.
An organization can have up to 50 active rules of its own, edited built-in rules included, and up to 10 active rules watching the same entity, like deployments.
Post inbox rules to Slack
Besides the people a rule notifies, you can post every notification of an inbox rule to Slack channels, from Also post to in the rule's editor. Each channel gets the rule wherever it happens in your organization.
The rule has to be saved first. The first time, Also post to offers Connect Slack: it installs the nullplatform app in your Slack workspace, and you only do it once per organization. Announcements use the same connection.
The nullplatform app only posts to channels it's a member of, private channels included. Invite it to every channel you want to post to. The channel picker marks the ones it isn't in yet with App not in channel.
If the connection stops working, for example because the app was removed from your workspace, Also post to shows Slack is disconnected and a Reconnect Slack button. Nothing is posted until you reconnect it, and the rule keeps its channels.
What a Slack message looks like
Each post carries the notification's title in bold, its body, and a button that opens it in nullplatform. Maintenance, degraded, and outage notifications also show their severity.
What triggers a rule
A rule's source says what makes it fire:
| Source | Fires when | Example |
|---|---|---|
{ "kind": "invoked" } | Your code calls the emission endpoint. | A CI scanner reports a vulnerability. |
A source name, like "deployment" | The entity changes status in nullplatform. | A deployment in a given namespace fails. |
{ "kind": "stale", ... } | An entity stays in a status for too long. | A deployment has been running for more than an hour. |
A GET request to /communications/inbox_rule/source lists the source names you can use and the statuses and fields each one exposes.
The rest of this page focuses on invoked rules, the only kind you can send through the API.
Create an invoked rule with the API
Send a POST request to https://api.nullplatform.com/communications/inbox_rule with the rule definition:
curl -L -X POST 'https://api.nullplatform.com/communications/inbox_rule' \
-H "Authorization: Bearer $NP_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"id": "acme.security_finding",
"source": { "kind": "invoked" },
"context_schema": {
"type": "object",
"required": ["id", "service", "owner_id"],
"properties": {
"id": { "type": "string", "description": "The scanner finding ID" },
"service": { "type": "string", "description": "The affected service" },
"summary": { "type": "string", "description": "One line on what was found" },
"owner_id": { "type": "integer", "description": "The nullplatform user who owns the service" }
}
},
"category": {
"group": "security",
"label": "Security findings",
"description": "Vulnerabilities your CI scanners report.",
"configurable": true,
"allows_all": true,
"default_mode": "involved"
},
"severity": "degraded",
"audience": [
{
"as": "owner",
"who": { "user": "${{ context.owner_id }}" },
"action": "acme.security_finding",
"content": {
"title": "Security finding on ${{ context.service }}",
"body": "${{ context.summary }}"
}
}
]
}'
Add ?dry_run=true to validate the definition without saving it.
The parts of a rule
id: lowercase words with exactly one dot, likeacme.security_finding. It's also the rule's ID in inbox settings, and it can't change later. It can't start withplatform., and using a built-in rule's ID edits that built-in rule instead of creating a new one.context_schema: a JSON Schema for the data you send each time. An invoked rule must requireid, because sends are idempotent by it. Only basic keywords are allowed:type,properties,required,enum,minLength,maxLength,minimum,maximum,items,writeOnly,description, and a booleanadditionalProperties.category: how the rule shows up in inbox settings. All six fields are required.descriptionis one line of up to 70 characters.allows_alllets people choose Everything for this rule under a resource, and it requiresconfigurable.default_mode(off,involved, orall) is what people get until they change it. For email, setchannels.email.default_modetoo: it defaults tooff.severity:informational,maintenance,degraded, oroutage. It sets the badge color.audience: one entry per reading. Each entry is a separate notification with its own wording, named byas.actionmust be the rule ID or start with it (for example,acme.security_finding.unowned).visible_to(optional): the one NRN the rule applies under, like["organization=1:account=2"]. It defaults to the organization root. Sends for resources outside it are refused.
Templates read the context you send with ${{ context.<field> }}. Titles and bodies accept inline Markdown (**bold**, *italic*, `code`), and bodies also accept links and lists.
To word a reading differently by email or in Slack, add content.email (subject, title, body) or content.slack (title, body) next to its title and body. Any field you leave out falls back to the reading's own text.
Who receives it
Each audience entry picks its recipients with who:
{ "user": "${{ context.owner_id }}" }: one user ID from the context. Pass a list of templates and the first one that renders wins.{ "can": "approval_request:approve" }: everyone who holds that action on the resource.- No
who: only the people who chose Everything for this rule on the resource or above it.
Add a fallback entry, with the same fields, to reach someone else when who resolves nobody. For example, a security lead when the service has no owner.
Send a notification
Once the rule exists, send it from your pipeline, workflow, or script. Send a POST request to https://api.nullplatform.com/communications/inbox_rule/<id>/emission with the resource the event is about (nrn) and the data the rule's templates read (context):
curl -L -X POST 'https://api.nullplatform.com/communications/inbox_rule/acme.security_finding/emission' \
-H "Authorization: Bearer $NP_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"nrn": "organization=1:account=2:namespace=3:application=4",
"context": {
"id": "finding-8812",
"service": "payments-api",
"summary": "CVE-2026-1234 in a transitive dependency",
"owner_id": 50
}
}'
A successful send answers 201:
{
"emitted": [{ "id": "6f1c2b9e-3a41-4f0e-9d55-2b7c1e0a9f13", "as": "owner", "recipients": 1 }],
"rejected": 0
}
Note:
recipientsandrejectedare counts, never user IDs.
What happens to the recipients
- Access is checked. A recipient who can't see the resource at
nrndoesn't receive the notification and is counted inrejected. - Inbox settings are applied. People who turned the rule off for that resource don't receive it.
- Followers are added. People who chose Everything for the rule on that resource, or above it, receive it too, when the rule allows it and at least one reading reached someone. They get the reading without
whoif the rule declares one, or else the first reading that reached someone.
Retries are safe
Sends are idempotent by context.id, so your pipeline can retry without notifying anyone twice:
- Sending the same
idfor the samenrnagain answers200, marks each entry inemittedwith"existing": true, and creates nothing. - Sending the same
idfor a differentnrnanswers409.
Use a distinct context.id per resource. Reusing one under another resource is refused.
Test before you send
- Add
?dry_run=trueto see what would be sent, to how many people, and why a reading would reach nobody. Nothing is written. - Add
"deliver_to": "me"to the body to send the first reading to yourself only.
Responses
| Status | Meaning |
|---|---|
201 | At least one notification was created. |
200 | A retry of an earlier send, or nobody was left to notify. |
202 | The rule declares a delay or batch behavior, so delivery was deferred. |
400 | The context doesn't match context_schema, the NRN isn't valid or is outside your organization, or the rule isn't invoked. |
404 | The rule doesn't exist, is deactivated, or doesn't apply to that NRN. |
409 | That context.id was already sent for another resource. |
Manage rules with the API
| Request | What it does |
|---|---|
GET /communications/inbox_rule | Lists the built-in rules and your organization's own. |
GET /communications/inbox_rule/<id> | Returns one rule. |
PUT /communications/inbox_rule/<id> | Replaces one of your rules. Add ?dry_run=true to validate only. |
DELETE /communications/inbox_rule/<id> | Deactivates one of your rules. |
GET /communications/inbox_rule/schema | Returns the JSON Schema of a rule definition, for editor completion. |
Editing a rule never changes notifications that were already sent.
Next steps
- Communications: your inbox, inbox settings, and announcements
- API keys: create the key your scripts use to get an access token