Skip to content

Notifiers

Fábio Luciano edited this page Jun 16, 2026 · 7 revisions

Notifiers

Notifiers receive every event that passes their when expression — they are not tied to the scm.provider annotation. All support name, enabled, when; message-based ones support template. For most (slack, teams, discord) the template is optional — omit it for the built-in structured message. For email and grafana a template is required (the chart supplies a shipped default when you omit the field). See Templates → Supplying a template.

notifiers:
  slack:
    - name: prod-alerts
      enabled: true
      secretRef:
        name: slack-webhook
      channel: "#prod-alerts"
      when: 'event.Namespace == "production" && stateIn("failure", "error")'
      template: |
        :rotating_light: *{{.PipelineName}}* failed in *{{.Namespace}}*
        Run: {{.RunName}} · Commit: `{{ .CommitSHA | trunc 8 }}`
        {{if .TargetURL}}<{{.TargetURL}}|View logs>{{end}}

Tip: without a when, a notifier fires for every state of every run — including running. Production configs almost always want stateIn("failure", "error") or terminal-states-only.

Slack

  • Auth: incoming webhook (webhook_url.secretRef.name → key webhook_url) or bot token (bot_token.token.secretRef.name → key token, plus bot_token.channel_id).
  • Extras: channel, username, icon_emoji. Message uses Slack mrkdwn.

Microsoft Teams

  • Auth: incoming webhook URL (webhook_url.secretRef.name → key webhook_url).
  • Rendered as an Adaptive Card; keep templates concise.

Discord

  • Auth: webhook URL (webhook_url.secretRef.name) or bot token (bot_token.* with channel snowflake ID). Extra: username.

PagerDuty

  • Auth: Events API v2 integration key (integration_key.secretRef.name → key integration_key).
  • Extra: severity. No template — the relay builds the alert payload. Pair with when: 'stateIn("failure", "error")' (and resolve flows on success).

Datadog

  • Auth: API key (api_key.secretRef.name → key api_key). Extras: site (e.g. datadoghq.eu), tags.
  • Emits Datadog events for correlation on dashboards/monitors.

Grafana annotations

Posts a deployment/event marker to the Grafana Annotations API — the vertical line that lets you correlate "the graph changed here" with "we deployed here".

grafana:
  - name: deploy-markers
    enabled: true
    url: https://grafana.company.example.com
    token:
      secretRef:
        name: grafana-token              # service-account token, key "token"
    tags: ["deploy"]                    # added to: tekton-events-relay, <state>
    when: 'isPipelineRun() && event.PipelineName.startsWith("deploy-") && stateIn("success", "failure")'

The annotation timestamp is the run's finish time. The marker template is required: in the Helm chart it is supplied inline, via configmapRef, or omitted to use the shipped default (deploy-marker.tmpl, {{.PipelineName}} {{.State}} ({{.RunName}})). See Templates.

  • Auth: a Grafana service-account token (token.secretRef, key token), sent as Authorization: Bearer. Grafana's API does not accept OAuth2 client-credentials tokens, so there is no oauth2 option here. The token file is re-read on every request, so rotating the Secret applies without a pod restart.

Email (SMTP)

Sends pipeline events as email over SMTP (STARTTLS on 587 by default, implicit TLS on 465, or unencrypted in-cluster relays on 25).

email:
  - name: oncall
    enabled: true
    host: smtp.example.com
    from: ci@example.com
    to: ["oncall@example.com"]
    # subject and template are required; supply inline, via configmapRef,
    # or omit to use the shipped defaults (email-subject.tmpl / email-default.tmpl):
    # subject:
    #   value: "[tekton] {{ .PipelineName }} — {{ .State }}"
    # template:
    #   configmapRef:
    #     name: my-email-templates
    #     key: body.tmpl
    html: false
    when: 'isPipelineRun() && stateIn("failure", "error")'
  • Auth: optional username + password (secretRef, key password). Omit for unauthenticated relays.
  • subject and template (body) are both required Go templates, resolvable the same way as every other template — inline string, configmapRef, or omitted→shipped default. See Templates.
  • html: true sends text/html; subject CR/LF is stripped to prevent header injection.

Sentry releases

Creates a Sentry release (version = CommitSHA, Sentry's recommended scheme) and marks a deploy to the environment (scm.context annotation, default production) — unlocking "this error first appeared in commit X". Fires only on success; creating an existing release is an upsert.

sentry:
  - name: sentry
    enabled: true
    org: acme                            # organization slug
    projects: ["api"]
    token:
      secretRef:
        name: sentry-token               # auth token, key "token"
    # base_url: https://sentry.company.example.com   # self-hosted
    when: 'isPipelineRun() && event.PipelineName.startsWith("deploy-")'
  • Auth: a Sentry auth token (internal-integration / org / personal — token.secretRef, key token), sent as Authorization: Bearer. Sentry's API does not accept OAuth2 client-credentials tokens, so there is no oauth2 option here. The token file is re-read per request, so Secret rotation applies without a pod restart.

Generic webhook

Sends the event as JSON to any HTTP endpoint — the escape hatch for systems without a dedicated notifier.

webhook:
  - name: devlake
    enabled: true
    url:
      secretRef:
        name: devlake-webhook            # key "url"
    headers:
      X-Source: tekton-events-relay
    transform: |
      {pipeline: .pipeline_name, result: .state, sha: .commit_sha,
       startedDate: .started_at, finishedDate: .finished_at}
    when: 'isPipelineRun() && stateIn("success", "failure")'
  • Auth (auth.type): bearer (auth.token.secretRef), basic (auth.username.secretRef + auth.password.secretRef), apikey (auth.token.secretRef + custom header), hmac (auth.hmac_secret.secretRef), or oauth2 (OAuth2 client credentials — auth.oauth2.{client_id,client_secret,token_url}).
    • OAuth2 (type: oauth2) fetches the access token from token_url and sends it as Authorization: Bearer, auto-refreshed before expiry. grant_type defaults to client_credentials; for refresh_token, supply a pre-obtained refresh_token (the relay exposes no redirect, so do the interactive consent out of band). For bearer/basic/apikey/hmac, the underlying secret files are re-read on every request, so rotating the Secret applies without a pod restart.
  • transform: a gojq expression reshaping the event JSON into whatever schema the destination expects — see the DevLake example.

Jira (issue tracking)

Jira is a top-level jira: integration (sibling of scm: / notifiers:), not a chat notifier — it acts on the work item linked to the run. Each instance has actions of type comment or transition.

jira:
  - name: default
    enabled: true
    base_url: https://yourorg.atlassian.net   # Cloud, or your Data Center URL
    auth:
      email: ci@example.com                   # set ⇒ Cloud basic auth; omit ⇒ Data Center bearer (PAT)
      token:
        secretRef:
          name: jira-token                    # API token / PAT, key "token"
    actions:
      - name: result-comment
        type: comment
        enabled: true
        when: 'isPipelineRun() && stateIn("success", "failure", "error")'
        # template:                            # omit for the shipped default (jira-comment.tmpl)
        #   value: |                            # …inline, or
        #     Pipeline {{ .State }}: {{ .PipelineName }}
        #   configmapRef:                       # …from a ConfigMap (name + key)
        #     name: jira-templates
        #     key: comment.tmpl
      - name: mark-done
        type: transition
        enabled: true
        transition: "Done"                    # transition name (case-insensitive) or numeric id
        when: 'isPipelineRun() && event.State == "success"'
  • Linking the issue: the target key (e.g. PROJ-123) comes from the tekton.dev/tekton-events-relay.jira.issue-key annotation. The TriggerBinding extracts it from the branch name or PR/MR title via a CEL overlay — no Tekton task required. Events without the annotation are skipped.
  • comment: posts the rendered template on the issue (REST v2, plain text). The body template is a resolvable reference (value inline or configmapRef: {name, key}); omit it for the default shipped in the templates ConfigMap (jira-comment.tmpl).
  • transition: resolves the configured transition against the issue's available transitions and applies it; if that transition isn't currently available, the action is a no-op rather than an error.
  • Auth: Cloud uses email + API token (basic); Data Center sends the token as a bearer PAT. A Data Center instance fronted by an OAuth2 server (or a Cloud service-account OAuth credential) can instead use auth.oauth2 client credentials — sent as Bearer, auto-refreshed before expiry; grant_type defaults to client_credentials (refresh_token also supported). oauth2 is not combinable with email or a static token; the static token file is otherwise re-read per request (rotation without restart). insecure_skip_verify: true for self-signed Data Center.

Delivery semantics (all notifiers)

Outbound calls go through the shared retry policy (backoff + jitter, Retry-After aware). Failures are visible in /readyz, tekton_events_relay_notifier_retries_total and, for permanent errors with the DLQ enabled, replayable.

Clone this wiki locally