Notifications
sfdt can push deployment events and org-health snapshots to Slack, Microsoft Teams, Google Chat, email, a generic webhook, or Grafana Loki — with per-channel event filtering and severity routing.
The multi-channel channels[] system is gated by notifications.enabled. The older
single-Slack shape (notifications.slack.webhookUrl) is still honored when
features.notifications: true. See Configuration.
Configuration
{
"features": { "notifications": true },
"notifications": {
"enabled": true,
"summary": { "enabled": false },
"channels": [
{
"type": "slack",
"name": "team-deploys",
"webhookUrlEnv": "SLACK_WEBHOOK_URL",
"severityThreshold": "warn",
"events": ["deploy-failure", "snapshot"]
},
{
"type": "teams",
"name": "ops",
"webhookUrlEnv": "TEAMS_WEBHOOK_URL",
"severityThreshold": "fail",
"events": ["snapshot"]
},
{
"type": "googlechat",
"name": "gchat-ops",
"webhookUrlEnv": "GCHAT_WEBHOOK_URL",
"severityThreshold": "warn",
"events": ["deploy-failure", "snapshot"]
},
{
"type": "email",
"name": "admins",
"from": "ci@example.com",
"to": ["admin@example.com"],
"smtp": { "hostEnv": "SFDT_SMTP_HOST", "portEnv": "SFDT_SMTP_PORT", "userEnv": "SFDT_SMTP_USER", "passwordEnv": "SFDT_SMTP_PASS" },
"severityThreshold": "fail",
"events": ["snapshot"]
},
{
"type": "webhook",
"name": "loki",
"format": "loki",
"url": "https://logs-prod.grafana.net/loki/api/v1/push",
"severityThreshold": "warn",
"events": ["snapshot"]
}
]
}
}Secrets are referenced by env-var name, never inline. Webhook URLs and SMTP credentials are
given as the name of an environment variable (webhookUrlEnv, urlEnv, SMTP *Env) — the
value is read from process.env at send time and is never stored in config. Inline
webhookUrl/url are still supported for back-compat but discouraged.
The same applies to webhook auth headers — use headersEnv, not headers, for anything
secret.
Authenticating a webhook channel
headers takes literal values, so a token placed there is written into .sfdt/config.json. Use
headersEnv instead — it maps a header name to the name of the env var holding its value:
{
"type": "webhook",
"name": "n8n",
"webhookUrlEnv": "HARNESS_WEBHOOK_URL",
"headersEnv": { "X-Auth-Token": "HARNESS_WEBHOOK_TOKEN" }
}headersEnv overrides a literal headers entry of the same name, so an existing config can adopt
it one key at a time. If a named env var is unset or empty, that channel fails with an error
naming the variable — it does not fall back to sending the request unauthenticated. Other channels
are unaffected.
Works with n8n Header Auth, Cloudflare Access service tokens, and any gateway expecting a shared-secret header.
Channel fields
| Field | Applies to | Meaning |
|---|---|---|
type | all | slack | teams | googlechat | email | webhook |
name | all | Friendly label (shown in CLI/GUI results) |
webhookUrl / webhookUrlEnv | slack, teams, googlechat | Incoming-webhook URL (inline or env-var name) |
url / urlEnv | webhook | Endpoint URL (inline or env-var name) |
format | webhook | json (default) or loki (Grafana Loki push) |
headers | webhook | Extra HTTP headers (literal values) |
headersEnv | webhook | Header name → name of the env var holding its value. Use this for auth tokens. |
from, to, smtp | Sender, recipients, and SMTP env-var names | |
severityThreshold | all | Minimum snapshot severity to receive (ok | warn | error | fail) |
events | all | Which events this channel receives (omit = all) |
Email uses nodemailer, an optional dependency that is lazy-loaded only when an email channel
is configured.
Events & severity routing
- Lifecycle events —
deploy-success,deploy-failure,test-failure,release-created,harness-escalation— are sent to any channel whoseeventsfilter allows them. - The
snapshotevent (an audit/monitor run) is routed by severity: a channel receives it only when the snapshot’s worst status is at or above the channel’sseverityThreshold.
Severity rank: ok < warn < error < fail.
AI executive-summary digests
Set notifications.summary.enabled = true to have snapshot notifications carry a 1–2 paragraph AI
executive summary (top issue + next steps) instead of the raw findings list. It requires
features.ai and an available AI provider; the summary is computed once and reused across
channels, and falls back to the normal body when AI is unavailable. The wording is controlled by
the editable monitor-summary prompt (see AI Providers).
Commands
sfdt notify deploy-failure --org prod --message "Release 1.4 failed"
sfdt notify snapshot --type monitor # push the latest monitor snapshot
sfdt notify snapshot --type audit
sfdt monitor all --notify # run + notify in one step
sfdt audit all --notifySee Org Health & Operations for the audit/monitor commands.
What leaves your machine
Every notification body is redacted before it is sent — session ids, force:// auth URLs, bearer
tokens, private-key blocks and password-shaped CLI arguments are replaced with [REDACTED]
markers. This applies to all channel types, including email and the snapshot object embedded
in webhook and Loki payloads.
Since 0.24.0. Two gaps closed here. Redaction ran per webhook shape and skipped the email
branch, so an email channel relayed run output through your own SMTP server unredacted. And the
destination check covered webhookUrl/url but not an email channel’s to list — so a
committed .sfdt/config.json could name a recipient and mail your org snapshots there with no
warning. A literal recipient list now needs the same SFDT_ALLOW_UNSAFE_CONFIG=1 opt-in as a
literal webhook URL. Use webhookUrlEnv / environment-sourced values for anything the
repository should not fix.
Since 0.25.0: webhook redirects are refused. A 3xx response used to be followed, and
307/308 preserve the method and the body — so an endpoint answering
307 Location: https://elsewhere.example received your notification body along with any
token in headersEnv. That defeated the destination check rather than merely lacking
depth: the check validates the URL you configured and has no say over where a redirect
points. A redirect is now an error naming the Location. Configure the final URL directly.
Surfaces
- GUI — the Notifications page lists configured channels (secrets redacted), shows whether the AI summary is on, and offers a “send test to all channels” button.
- MCP —
sfdt_notifypushes the latest snapshot. - CI — the generated pipelines run
monitor all --notifyon a schedule (see CI/CD Integration).