Inbox
Persistent per-user notifications: messages sent on admin-defined channels land in each recipient's inbox and stay there - with read state, priority, thread grouping, interactive actions, receipts, and delivery controls (scheduled delivery, expiry, self-destruct) - until acted on or swept by retention.
When to reach for it: in-app notification centers, agent-to-human handoff messages, approval prompts, job status updates - anything a user must be able to find later and mark as handled.
When not to: ephemeral realtime fan-out with no persistence or read state belongs to Pub/Sub; service-to-service work dispatch belongs to Message Queue; an append-only activity feed belongs to Timeline. Email is out of scope entirely.
Concepts
- Channels are named categories (
docs4.alerts) created by a platform admin or service principal. A channel carries policy that every message on it inherits: default priority, retention, who may send, whether actions and scheduled delivery are allowed. See Channels and delivery. - Fan-out at send - one send to N recipients creates N independent
messages, each with its own
message_idand its own read state. - Read state is per message - read/unread, dismissed, archived - and aggregate unread counts come broken down by channel, priority, and thread.
- Threads group related messages under a caller-chosen
thread_id(a job id, a conversation id) and get their own summary endpoint. - Actions are buttons attached to a message (
approve,link,retry, ...). Executing one records an auditable result; the server never calls the action'stargetitself - kinds and targets are hints for the client rendering the inbox. - Priority is one of
low,normal,high,urgent; omitted on send, it falls back to the channel's default (verified: a plain send on a default channel arrived asnormal).
Sending
Anyone authenticated can send on a channel with an empty producer
allowlist; restricted channels reject other senders with
403 producer_not_allowed.
snug inbox send --to user_123 --channel KunCSRQzLPryQwbHxSfp \
--title "Deploy finished" --msg "Build 421 is live"
snug inbox send --to user_123 --to user_456 --channel KunCSRQzLPryQwbHxSfp \
--msg "Ready" --priority high --thread deploy-421
snug inbox batch-send --file messages.json # JSON array of send requestsRe-sends are made safe with an idempotency key, scoped per channel and
recipient - verified: four sends with --idempotency-key docs4-idem-1
all returned the same message_id, and exactly one message existed.
Over HTTP, POST /api/v1/inbox/messages returns 202 with the created
ids (captured live; actions attached here because the CLI send command
does not take actions):
curl -X POST -H "Authorization: Bearer $SNUG_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"channel_id": "KunCSRQzLPryQwbHxSfp",
"recipient_user_ids": ["docs-wave"],
"title": "Approve rollout", "body": "Promote canary to 100%?",
"priority": "high",
"actions": [{"id": "approve", "label": "Promote", "kind": "approve"},
{"id": "docs", "label": "Runbook", "kind": "link",
"target": "https://example.com/runbook"}]}' \
http://localhost:4000/api/v1/inbox/messages{
"status": 202,
"msg": "Accepted",
"data": {
"message_ids": ["RqehZAdRfEmNDmnjUSHR"],
"status": "delivered",
"scheduled": false
}
}Reading and read state
snug inbox list --unread
snug inbox list --channel KunCSRQzLPryQwbHxSfp --priority high --page-size 20
snug inbox get --message-id RqehZAdRfEmNDmnjUSHR --mark-read
snug inbox read --message-id RqehZAdRfEmNDmnjUSHR # returns unread_total
snug inbox unread --message-id RqehZAdRfEmNDmnjUSHR
snug inbox countscounts returns the total plus by_channel, by_priority, and
by_thread maps. Reading another user's message fails with
403 insufficient_permissions ("cannot access another user's message"),
not a silent 404 - verified with a second identity.
The HTTP list endpoint also takes filter expressions in the
shared query grammar
(filter=lifecycle:eq:read) over the tagged fields - channel, recipient,
thread, priority, lifecycle, and the read/dismissed/archived/scheduled
flags.
Threads
snug inbox threadsEach summary carries message_count, unread_count,
highest_priority, and the latest message's id and title - verified with
a six-message docs4-job-88 thread.
Dismiss, archive, bulk
Dismissed and archived messages keep their data but drop out of default
lists until requested back with --include-dismissed /
--include-archived (verified: list total went 10 -> 8 and back).
snug inbox dismiss --message-id msg_id # dismiss
snug inbox dismiss --message-id msg_id --archive # archive instead
snug inbox bulk --operation mark_read --unread-only --channel KunCSRQzLPryQwbHxSfp
snug inbox bulk --operation delete --message-id id1 --message-id id2Bulk operations are mark_read, mark_unread, dismiss, archive,
delete, selected by explicit ids or by filter flags, and return
affected plus the new unread_total.
Actions and receipts
snug inbox action --message-id RqehZAdRfEmNDmnjUSHR --action-id approve \
--idempotency-key act-1
snug inbox receipt --message-id msg_id --kind read --device "ios-app"Executing an action records a completed MessageActionResult (default
result {"acknowledged": true}, or the payload you pass); repeating with
the same idempotency key returns the same result_id instead of a new
record. An unknown action id is 404 action_not_found. A read receipt
also marks the message read (verified) - delivered receipts only append
to the receipt log.
Channels and delivery controls
Channel administration (admin or service principal only), producer allowlists, required technical notes, scheduled delivery, expiry, and self-destruct are on their own page: Channels and delivery.
Limits and configuration
Message bodies up to 32 KiB, 1000 recipients per send, 8 actions per
message, 500 items per bulk operation, and 100 messages per batch-send by
default - all tunable via the INBOX_* variables in the
Inbox CONFIG reference.
Reference
- Inbox API - every endpoint, callable
- Related: Pub/Sub for ephemeral fan-out, Timeline for activity feeds, Message Queue for service-to-service work