TurboPanel Docs
Database

Notifications

The notification catalogue rows, delivery channels, routing rules and per-channel attempts.

Tables on this page: attempt · channel · notification · rule. Generated from migration 0004_schema_comments; see How to read this.

attempt

Delivery-attempt ledger: one row per event per routed channel, inserted before the send so a crash leaves a pending row; retried by the maintenance tick.

ColumnTypeNullDefaultDescription
id (PK)uuidnouuidv7()Primary key (uuidv7(), time-ordered).
created_attimestamp(3) with time zonenonow()Row creation time.
updated_attimestamp(3) with time zonenonow()Last write time; the ORM sets it to now() on every update.
channel_iduuidnoFK → channel.id (on delete cascade).
organization_iduuidyesFK → organization.id (on delete cascade). Organization the event belongs to; NULL for an instance-scoped event.
eventtextnoCatalogue code (NOTIFICATION_EVENTS), same vocabulary as notification.event.
severitytextnoinfo, warning or critical, copied from the event's catalogue definition.
payloadjsonbyesRendered non-secret message as JSON: event, severity, title, body, organizationId, organizationName, targetType, targetId, context, at.
statustextno'pending'pending (default, not yet sent), sent, failed (retry due) or abandoned (after 5 failed attempts); retries pick up pending and failed.
attemptsintegerno0Number of send attempts so far, bumped in SQL by the sender; the row is abandoned once it reaches 5.
next_attempt_attimestamp(3) with time zoneyesEarliest time the retry sweep may pick the row up: now plus 2 min grace on insert, then backoff of 1, 5, 25 and 125 min; NULL once sent.
sent_attimestamp(3) with time zoneyesWhen a send succeeded; NULL until then.
last_errortextyesShort failure code from the last attempt (http_503, timeout, network, address_scheme_not_https), never a body or the address.

Constraints and indexes

  • Index idx_attempt_pending: (status, next_attempt_at)
  • Index idx_attempt_channel_created: (channel_id, created_at)
  • Check attempt_event_check: event IN ('server.offline', 'fleet.mass_disconnect', 'server.deleted', 'server.daemon_key_revoked', 'access.grant_created', 'access.grant_revoked')
  • Check attempt_severity_check: severity IN ('info', 'warning', 'critical')
  • Check attempt_status_check: status IN ('pending', 'sent', 'failed', 'abandoned')

channel

An address notifications can be delivered to, owned by the instance, one organization or one user per scope; addresses of secret kinds are sealed.

ColumnTypeNullDefaultDescription
id (PK)uuidnouuidv7()Primary key (uuidv7(), time-ordered).
created_attimestamp(3) with time zonenonow()Row creation time.
updated_attimestamp(3) with time zonenonow()Last write time; the ORM sets it to now() on every update.
scopetextnoOwner kind: instance, organization or user; the owner columns must agree with it (CHECK).
organization_iduuidyesFK → organization.id (on delete cascade). Owning organization, set only when scope is organization and NULL otherwise.
user_iduuidyesFK → user.id (on delete cascade). Owning user, set only when scope is user; a personal channel follows the user across organizations.
kindtextnoTransport: email, webhook, slack, discord, telegram or push (push is registered by the store apps, never typed).
labeltextnoOwner-chosen display name such as 'Ops Slack'.
addresstextnoDelivery address: plain for email, else a sealed tpsecret envelope (webhook, Slack or Discord URL, Telegram token/chatId, push token).
signing_secrettextyesSealed tpsecret HMAC key for the generic webhook kind (X-TurboPanel-Signature: sha256=...); NULL for other kinds.
verified_attimestamp(3) with time zoneyesWhen the address was confirmed; stamped at creation for own or member emails, NULL means unverified and email delivery is skipped.
disabled_attimestamp(3) with time zoneyesSet when the owner pauses the channel; it keeps its rules but receives nothing until resumed.
created_by_user_iduuidyesFK → user.id (on delete set null). User whose session created the channel (provenance, set NULL on user delete); differs from user_id for org and instance channels.

Constraints and indexes

  • Index idx_channel_organization: (organization_id)
  • Index idx_channel_user: (user_id)
  • Check channel_scope_check: scope IN ('instance', 'organization', 'user')
  • Check channel_kind_check: kind IN ('email', 'webhook', 'slack', 'discord', 'telegram', 'push')
  • Check channel_owner_check: (scope = 'instance' AND organization_id IS NULL AND user_id IS NULL) OR (scope = 'organization' AND organization_id IS NOT NULL AND user_id IS NULL) OR (scope = 'user' AND user_id IS NOT NULL AND organization_id IS NULL)

notification

One bell inbox row per event per recipient user, written by emitNotification for every member or manager the event's audience names.

ColumnTypeNullDefaultDescription
id (PK)uuidnouuidv7()Primary key (uuidv7(), time-ordered).
created_attimestamp(3) with time zonenonow()Row creation time.
user_iduuidnoFK → user.id (on delete cascade). Recipient of the inbox row; one row per person the event reached, chosen by the event's audience (members or managers).
organization_iduuidyesFK → organization.id (on delete cascade). Organization the event belongs to; NULL for an instance-scoped event such as fleet.mass_disconnect.
eventtextnoCatalogue code (NOTIFICATION_EVENTS): server.offline, fleet.mass_disconnect, server.deleted, server.daemon_key_revoked, access.grant_*.
severitytextnoinfo, warning or critical, copied from the event's catalogue definition.
titletextnoOne-line sentence rendered from the catalogue's title template at emit time; also the email subject.
bodytextyesFuller sentence rendered from the catalogue's body template; NULL when the event defines none.
target_typetextyesCatalog entity kind the event is about (server, or a grant's entity type) so the bell can link to it; NULL when there is no target.
target_iduuidyesUUID of the target_type entity; not a foreign key, so it may outlive the entity.
contextjsonbyesSmall non-secret facts the sentence was rendered from, such as serverName, lastSeenAt, count, actorEmail, permissionKey, subjectKind, subjectId.
read_attimestamp(3) with time zoneyesWhen the recipient marked it read (Mark all read); NULL while unread and counted in the badge.
dismissed_attimestamp(3) with time zoneyesWhen the recipient dismissed the row from the bell; NULL while it is still shown.

Constraints and indexes

  • Index idx_notification_user_created: (user_id, created_at)
  • Check notification_event_check: event IN ('server.offline', 'fleet.mass_disconnect', 'server.deleted', 'server.daemon_key_revoked', 'access.grant_created', 'access.grant_revoked')
  • Check notification_severity_check: severity IN ('info', 'warning', 'critical')

rule

Subscription row saying which event (or *) reaches a channel at or above a severity floor; a channel with no rule receives nothing.

ColumnTypeNullDefaultDescription
id (PK)uuidnouuidv7()Primary key (uuidv7(), time-ordered).
created_attimestamp(3) with time zonenonow()Row creation time.
channel_iduuidnoFK → channel.id (on delete cascade).
eventtextnoOne catalogue event code, or * for every event; unique per channel.
min_severitytextno'info'Severity floor info (default), warning or critical; delivered only when the event's severity ranks at or above it.

Constraints and indexes

  • Unique index uniq_rule_channel_event: (channel_id, event)
  • Check rule_event_check: event IN ('*', 'server.offline', 'fleet.mass_disconnect', 'server.deleted', 'server.daemon_key_revoked', 'access.grant_created', 'access.grant_revoked')
  • Check rule_min_severity_check: min_severity IN ('info', 'warning', 'critical')
Edit on GitHub

Last updated on

On this page