Monitor faster with argocd-notifications: send sync, health, and failure notifications to Slack, Teams, email, or webhook through triggers, templates, and subscriptions.

In episode 16 we automated image updates — and automation creates a new need: knowing what's happening. When Image Updater changes a tag at night, who knows? When a sync fails in production, who gets paged? The answer is in argocd-notifications: ArgoCD's built-in notification system that sends messages to Slack, Teams, email, webhooks, and even GitHub/GitLab status — all configured through YAML in Git.
Why does this matter? An invisible deploy is the biggest source of anxiety for operations teams. The right notifications turn the team's role from "guessing whether the deploy succeeded" into "waiting for certain information". What's more, notifications are the first layer of observability — which we'll complete with metrics and logging in episode 22.
Since ArgoCD v2.4, notifications are integrated into the application controller — no separate binary needed. All configuration lives in two objects:
argocd-notifications-cm ConfigMap — contains triggers, templates, services, and subscriptions.argocd-notifications-secret Secret — contains channel tokens/credentials.The architecture is three core concepts:
| Concept | Function |
|---|---|
| Trigger | The condition that determines when a notification is sent |
| Template | The message format that determines what is sent |
| Subscription | The binding of trigger + template to a specific channel |
A trigger is a condition (evaluated from the Application status) that fires a notification. Built-in examples: on-sync-succeeded, on-sync-failed, on-health-degraded. Trigger definition:
trigger.on-sync-succeeded: |
- when: app.status.sync.status in ('Synced')
send: [app-sync-succeeded]
trigger.on-deploy-failed: |
- when: app.status.health.status in ('Degraded') and app.status.sync.status in ('OutOfSync')
send: [deploy-failed]The when field uses expressions (can contain AND/OR logic), and send refers to the template name to use.
Note
Trigger conditions are read from the Application status — to test complex expressions, use argocd app get <name> first to see the actual status.sync.status and status.health.status values.
Templates define the message content. They can use Application variables and conditionals:
template.app-sync-succeeded: |
message: |
✅ Application {{.app.metadata.name}} berhasil di-sync.
Environment: {{.app.spec.destination.namespace}}
slack:
attachments: |
[{ "color": "#2eb886", "title": "Deploy sukses" }]
template.deploy-failed: |
message: |
⚠️ Deploy {{.app.metadata.name}} GAGAL!
Sync: {{.app.status.sync.status}} | Health: {{.app.status.health.status}}Variables like app.metadata.name are filled in from the Application object; if/else blocks let the message change based on conditions (e.g. showing the namespace only if present). Messages can be plain text or rich attachments for Slack/Teams.
Warning
Templates use Go template syntax with double curly braces — and are only valid inside a ConfigMap (YAML). Never write expressions like app.metadata.name wrapped in double curly braces in normal narrative text because it will be interpolated incorrectly.
A subscription determines who receives which notification. Two ways:
subscriptions: |
- recipients:
- slack:deployments
triggers:
- on-sync-succeeded
- on-sync-failed
- recipients:
- email:ops@company.com
triggers:
- on-health-degradedapiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: billing-api
namespace: argocd
annotations:
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: deployments
notifications.argoproj.io/subscribe.on-sync-failed.email: ops@company.comThe annotation pattern is: notifications.argoproj.io/subscribe.<trigger>.<service>: <recipient>. This gives granular control — critical applications can send to a different channel.
Connections to channels are configured as a service in the ConfigMap, and tokens are stored in the Secret:
service.slack: |
token: $slack-token
service.webhook: |
url: https://hooks.example.com/gitopsapiVersion: v1
kind: Secret
metadata:
name: argocd-notifications-secret
namespace: argocd
stringData:
slack-token: xoxb-XXXX-YYYYChannels supported out of the box:
| Service | Recipient | Benefits |
|---|---|---|
| Slack | slack:<channel> | Team notifications, fast and richly formatted |
| Microsoft Teams | teams:<webhook> | Enterprise integration |
| Email (SMTP) | email:<address> | Formal archive, audit |
| Webhook | webhook:<url> | Custom/internal API integration |
| GitHub/GitLab | Commit status | Deploy status on the PR page |
The GitHub/GitLab service (service.github-commit-status) sets a status on the commit — very useful: PR reviewers immediately see a deploy success/failure marker without opening ArgoCD.
on-sync-succeeded trigger gives the team certainty that a change is live.on-sync-failed/on-health-degraded triggers direct on-call to the problem application earlier than waiting for user reports.Before rolling out to many channels, test with the CLI:
argocd notify trigger billing-api on-sync-succeeded
argocd notify trigger billing-api on-deploy-failed
argocd notify broadcast "Test broadcast"argocd notify trigger forcibly runs a trigger for a specific application — the fastest way to verify templates and channel connections without waiting for a real event.
argocd-notifications-secret); the ConfigMap only holds references like $slack-token.status.health.status) must be exact; a small mistake produces an empty message. Test with argocd notify trigger.when conditions.This episode closed the GitOps automation loop with human observability: the trigger/template/subscription architecture, configuration in the ConfigMap and Secret, Slack/Teams/email/webhook/commit-status channels, triggers based on sync and health, templates with variables and conditionals, and use cases from deploy notifications to audit trails.
The points you should take with you:
argocd notify trigger before rolling out configuration.Notifications tell us that something happened — but how is that event born in CI/CD? In the next episode 18 we assemble everything in CI/CD Pipeline Integration: GitHub Actions, GitLab CI/CD, and Jenkins that build images, update manifests, and hand the rest over to ArgoCD. See you in episode 18!