Send Alertmanager notifications to Google Chat (and more!)
calert uses Alertmanager webhook receiver to receive alerts payload, and pushes this data to Google Chat webhook endpoint.
Grab the latest release from Releases.
To run:
./calert.bin --config config.tomlYou can find the list of docker images here
docker pull ghcr.io/mr-karan/calert:latest
latest is the default Ubuntu 24.04 image. It includes a shell, CA certificates, and timezone data for production debugging. latest-scratch contains the same static binary, certificates, and timezone data in a smaller scratch image, but has no shell or package tools.
Here's an example docker-compose config with a custom message.tmpl mounted inside the container:
calert:
image: ghcr.io/mr-karan/calert:latest
ports:
- "6000:6000"
volumes:
- ./message.tmpl:/etc/calert/message.tmplHere's an example docker-compose config with a custom config.toml mounted inside the container:
calert:
image: ghcr.io/mr-karan/calert:latest
ports:
- "6000:6000"
volumes:
- ./config.toml:/app/config.sample.tomlRefer to config.sample.toml for instructions on how to configure calert.
All the config variables can also be supplied as Environment Variables by prefixing CALERT_ and replacing . (period) with __ (double underscores).
Example:
app.addresswould becomeCALERT_APP__ADDRESS
| Key | Explanation | Default |
|---|---|---|
app.address |
Address of the HTTP Server. | 0.0.0.0:6000 |
app.server_timeout |
Server timeout for HTTP requests. | 5s |
app.enable_request_logs |
Enable HTTP request logging. | true |
app.log |
Use debug to enable verbose logging. Can be set to info otherwise. |
info |
calert can load a map of different providers. The unique identifier for the provider is the room name. Each provider has it's own configuration, based on it's provider_type. Currently calert supports Google Chat but can support arbitary providers as well.
| Key | Explanation | Required | Default |
|---|---|---|---|
providers.<room_name>.type |
Provider type. Currently only google_chat is supported. |
no | google_chat |
providers.<room_name>.endpoint |
Webhook URL to send alerts to. | yes | - |
providers.<room_name>.max_idle_conns |
Maximum Keep Alive connections to keep in the pool. | yes | 50 |
providers.<room_name>.timeout |
Timeout for making HTTP requests to the webhook URL. | yes | 30s |
providers.<room_name>.template |
Template for rendering a formatted Alert notification. | yes | static/message.tmpl |
providers.<room_name>.thread_ttl |
Group-state retention period. It does not rotate a recurring GroupKey into a new Chat thread. | yes | 12h |
providers.<room_name>.proxy_url |
Specify proxy_url as your proxy endpoint to route all HTTP requests to the provider via a proxy. |
no | - |
providers.<room_name>.threaded_replies |
Whether to send threaded replies or not. Group mode always threads. | no | false |
providers.<room_name>.threading_mode |
alert (default) uses deterministic per-alert lifecycle keys. group uses one thread per Alertmanager GroupKey. |
no | alert |
providers.<room_name>.dedup_window |
Group-mode window for sequential identical payloads after successful delivery. | no | 2m |
providers.<room_name>.max_alerts_per_message |
Maximum alert sections in a group card. | no | 10 |
providers.<room_name>.redis.* |
Optional Redis address, password, db, and key_prefix for shared group state. | no | - |
providers.<room_name>.dry_run |
In case you're simply experimenting with calert config changes and you don't wish to send actual notifications, you can set true. |
no | false |
providers.<room_name>.retry_max |
Maximum number of retries | no | 3 |
providers.<room_name>.retry_wait_min |
Minimum time to wait before retrying | no | 1s |
providers.<room_name>.retry_wait_max |
Maximum time to wait before retrying | no | 5s |
calert supports Go templates for formatting alert messages. Templates have access to all alert fields and several helper functions.
| Function | Description | Example |
|---|---|---|
Title |
Title case string | {{ .Labels.alertname | Title }} |
toUpper |
Uppercase string | {{ .Labels.severity | toUpper }} |
toLower |
Lowercase string | {{ .Status | toLower }} |
Contains |
Check if string contains substring | {{ if Contains .Labels.alertname "CPU" }}...{{ end }} |
HasPrefix |
Check string prefix | {{ if HasPrefix .Labels.instance "prod" }}...{{ end }} |
HasSuffix |
Check string suffix | {{ if HasSuffix .Labels.job "exporter" }}...{{ end }} |
Replace |
Replace all occurrences | {{ Replace .Labels.instance ":" "_" }} |
TrimSpace |
Trim whitespace | {{ .Annotations.description | TrimSpace }} |
Default |
Provide default value | {{ .Annotations.runbook | Default "No runbook" }} |
reReplaceAll |
Regex replace | {{ reReplaceAll "\\d+" "X" .Labels.instance }} |
CurrentTime |
Current time (optional timezone) | {{ CurrentTime "Asia/Kolkata" }} |
ConvertTZ |
Convert time to timezone | {{ ConvertTZ .StartsAt "America/New_York" }} |
DurationSince |
Duration since time | {{ DurationSince .StartsAt }} |
json |
JSON-escape a dynamic value before inserting it into CardsV2 JSON | {{ json .Annotations.description }} |
For rich formatting with colors and structured layouts, use Google Chat's CardsV2 format by defining a cardsV2 template block:
{{- define "cardsV2" -}}
{
"cardId": {{ json (printf "alert-%s" .Fingerprint) }},
"card": {
"header": {
"title": {{ json .Labels.alertname }},
"subtitle": {{ json (.Status | Title) }}
},
"sections": [{
"widgets": [{
"decoratedText": {
"text": {{ json .Annotations.description }}
}
}]
}]
}
}
{{- end -}}
Google Chat's simple text webhook supports limited formatting:
- Bold:
*text* - Italic:
_text_ - Strikethrough:
~text~ - Monospace:
`text`
Note: HTML tags (like <font color="...">) and standard emoji shortcodes (:warning:) are not supported in simple text messages. For colors and rich formatting, use CardsV2 templates instead.
-
Alertmanager has the ability of group similar alerts together and fire only one event, clubbing all the alerts data into one event.
calertleverages this and sends all alerts in one message by looping over the alerts and passing data in the template. You can configure the rules for grouping the alerts inalertmanager.ymlconfig. You can read more about it here. -
Configure Alertmanager config file (
alertmanager.yml) and give the address of calert web-server. You can refer to the official documentation for more details.
You can refer to the following config block to route webhook alerts to calert:
route:
receiver: 'calert'
group_wait: 30s
group_interval: 60s
repeat_interval: 15m
group_by: ['room', 'alertName']
receivers:
- name: 'calert'
webhook_configs:
- url: 'http://calert:6000/dispatch'Alertmanager's repeat_interval controls how often alerts are re-sent while still firing. Deterministic thread keys keep the same alert lifecycle in the same thread and do not rotate when thread_ttl elapses. In group mode, thread_ttl controls only retention of deduplication and resolved-status state.
If you want fewer repeated messages:
- Increase
repeat_intervalin Alertmanager config - Increase
thread_ttlin group mode only when deduplication and resolved-status state should be retained longer
When using Kubernetes AlertmanagerConfig CRD, the receiver name is automatically prefixed with namespace/config-name/receiver. Use the room_name query parameter to override:
apiVersion: monitoring.coreos.com/v1alpha1
kind: AlertmanagerConfig
metadata:
name: my-config
namespace: monitoring
spec:
receivers:
- name: prod_alerts
webhookConfigs:
- url: http://calert:6000/dispatch?room_name=prod_alertscalert ships with a basic support for sending multiple related alerts under a same thread, working around the limitations by Alertmanager.
Alertmanager currently doesn't send any Unique Identifier for each Alert. The use-case of sending related alerts under the same thread is helpful to triage similar alerts and see all their different states (Firing, Resolved) for people consuming these alerts. calert tries to solve this by:
- Use the alert
fingerprintandstartsAtfields to derive a deterministic thread key. - Firing and resolved updates for the same alert lifecycle share a thread across restarts and replicas.
- A refire with a new
startsAtreceives a new thread key.
The above is per-alert threading (threading_mode = "alert", the default). calert also supports threading one whole Alertmanager group into a single thread with an aggregated message, and sharing dedup state across active-active instances via Redis. See docs/group-threading.md for setup.
calert exposes various metrics in the Prometheus exposition format.
Here's a list of internal app metrics available at /metrics:
| Name | Description | Data type |
|---|---|---|
calert_uptime_seconds |
Uptime of app (in seconds). | counter |
calert_start_timestamp |
UNIX timestamp since the app was booted. | gauge |
calert_http_requests_total |
Number of HTTP requests, grouped with labels like handler. |
counter |
calert_http_request_duration_seconds_{sum,count,bucket} |
Duration of HTTP request (in seconds). | histogram |
calert_alerts_dispatched_total |
Number of alerts dispatched to upstream providers, grouped with labels like provider and room. |
counter |
calert_alerts_dispatched_duration_seconds_{sum,count,bucket} |
Duration to send an alert to upstream provider. | histogram |
It also exposes Go process metrics in addition to app metrics, which you can use to monitor the performance of calert.
calert exposes an health endpoint at /ping.
A few notes on v2 migration:
v2 is a complete rewrite from scratch and is a breaking release. The configuration has changed extensively. Please refer to latest config.sample.toml for a complete working example of the config.
In case you're simply experimenting with calert config changes and you don't wish to send actual notifications, you can set dry_run=true in each provider.
Apart from the config, calert now determines the room based on the receiver specified in Alertmanager config. Previously, the room was identified with ?room query parameter in each HTTP request. However, since the Alert payload contains the receiver name, it's better to extract this information from the labels instead.
Here's an example of how Alertmanager config looks like. Notice the value of receiver (prod_alerts) should match one of provider.<room_name> (eg provider.prod_alerts) in your config.toml):
receivers:
- name: 'prod_alerts'
webhook_configs:
- url: 'http://calert:6000/dispatch'PRs on Feature Requests, Bug fixes are welcome. Feel free to open an issue and have a discussion first.
For deployment manifests like Helm, Kustomize, Nomad etc - they're placed under contrib folder and generally manintained by the community.
