Alerting
Email and webhook alerts for errors, updates and certificates.
sbox-ns can notify operators when it captures errors: Discord channel
messages via an incoming webhook, and plain-text email via SMTP. Both are
disabled by default, best-effort (a failed send is logged and never fails
the request that triggered it), and configured entirely through alerts.toml,
conf.d/ overrides, or NS_ environment variables.
What triggers an alert
Every error that reaches the shared error pipeline fans out to the log plus any configured channel:
- Unhandled request exceptions captured for
/admin/errors(5xx, plus 503 database outages). - Native endpoint-execution shadow failures (
EndpointShadowReporter): executor exceptions, 5xx results, and 409 data-integrity conflicts (SAVE_REGRESSION_BLOCKED/STALE_SAVE). Repeated identical conflicts are throttled so a looping autosave cannot flood the channel. - Compatibility-proxy captures (
ProxyErrorReporter) and handler-caught exceptions reported viaHandlerErrorReporter. - Data-plane storage errors (
INetworkStorageErrorAlertSink), e.g. unconfirmed saves.
Each alert carries the correlation ID, route, classification, project, and
timestamp so it can be matched against /admin/errors entries and logs.
Discord setup
- In the Discord channel: Edit Channel → Integrations → Webhooks → New Webhook, copy the webhook URL.
- Store the URL in a secret file (recommended) or directly in config:
# alerts.toml
[alerts.discord]
enabled = true
webhook_url_file = "secrets/discord_webhook_url"
username = "sbox-ns"
or with environment variables:
NS_ALERTS__DISCORD__ENABLED=true
NS_ALERTS__DISCORD__WEBHOOK_URL_FILE=/run/secrets/discord_webhook
webhook_url and webhook_url_file are mutually exclusive; setting both is
a validation error. Relative *_file paths resolve against the config
folder. The message is a single embed (title, description, route / status /
classification / correlation ID / project fields).
SMTP setup
# alerts.toml
[alerts.smtp]
enabled = true
host = "mail.example.com"
port = 587
username = "sbox-ns@example.com"
password_file = "secrets/smtp_password"
from = "sbox-ns@example.com"
to = "ops@example.com, oncall@example.com"
use_tls = true
password and password_file are mutually exclusive. Empty username
means no authentication. to accepts comma- or semicolon-separated
addresses. Mail is plain text with subject
[sbox-ns] <Classification> <Method> <Path> (<CorrelationId>) and a
field-list body; delivery uses a 10-second timeout and STARTTLS when
use_tls is true.
Secrets handling
alerts.discord.webhook_urlandalerts.smtp.passwordare flagged secret:sbox-ns config showprints"********"unless--show-secretsis passed, and the values never appear in logs or alert payloads.- Prefer
*_fileso secrets live outside the TOML files (same pattern asdatabase.postgres.password_file). The first line of the file is used.
Checking the configuration
sbox-ns config validate # parse + combination checks with file:line
sbox-ns config show # merged effective config, secrets redacted
sbox-ns config set alerts.discord.enabled true
sbox-ns config get alerts.smtp.host
config set/get work for every alerts.* key through the generic
mechanism — no special subcommands exist. There is no dry-run send command;
to verify delivery, enable a channel and trigger a real capture (for
example an invalid request against a test project), then confirm the
correlation ID matches between the Discord/email alert, the server log,
and /admin/errors.