abot: Alertmanager proxy for Telegram and Mattermost notifications
Published: 2026-05-12
abot is a small Go service that sits between Alertmanager and the notification channels. Alertmanager sends one webhook; abot fans it out to Telegram and Mattermost with proper formatting, retry logic, and proxy support.
Why not use Alertmanager receivers directly
Alertmanager has native Telegram and Slack receivers, but:
- The native Telegram receiver doesn't support HTML template with all the formatting we need.
- Mattermost uses a different webhook format than Slack — there's no native Mattermost receiver.
- On-prem clusters can't reach
api.telegram.orgwithout going through an HTTP proxy. Configuring that proxy in Alertmanager affects all receivers, not just Telegram. - abot provides a single place to manage notification logic — adding a new channel (PagerDuty, email) means changing abot, not Alertmanager config.
Architecture
Alertmanager
│ POST /alert (Alertmanager webhook format)
▼
abot (port 9444)
├── Telegram: api.telegram.org (via HTTP proxy if configured)
└── Mattermost: chat.example.com/hooks/...
abot runs as a Deployment in observability namespace on every cluster.
Helm chart
The custom chart lives at charts/abot/. Key values:
yamlreplicaCount: 1
image:
repository: registry.example.com/docker/iac/prometheus/abot2
tag: v0.78
service:
port: 9444
targetPort: 9100
serviceMonitor:
enabled: true
namespace: observability
interval: 30s
metricRelabelings:
- sourceLabels: [__name__]
regex: 'abot_.+'
action: keep # only keep abot's own metrics
resources:
limits:
cpu: "800m"
memory: "256Mi"
requests:
cpu: "200m"
memory: "128Mi"
Alertmanager configuration pointing to abot
yamlalertmanager:
enabled: true
config:
receivers:
- name: abot
webhook_configs:
- url: "http://abot.observability.svc.cluster.local:9444/alert"
send_resolved: true
route:
receiver: abot
group_by: ["alertname", "cluster", "namespace"]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
send_resolved: true ensures abot receives a notification when an alert clears. group_by prevents a single noisy incident from generating hundreds of individual messages.
Telegram proxy configuration
yamlconfig:
telegram:
bot_token: "${TG_BOT_TOKEN}"
chat_id: "-1003960777636"
proxy: "http://10.16.91.109:3128"
parse_mode: "HTML"
The proxy is an internal tinyproxy instance on the on-prem network. For YC clusters (sre, loadgds, demo) the proxy is not needed — they have direct internet access. The patch for YC environments removes the proxy field.
Message format
abot renders the Alertmanager payload using Go templates. The Telegram message uses HTML:
🔴 · CRITICAL · <a href="https://grafana.infra.test.antonnovikov.com">dashboard</a>
🖥 dev
📦 elasticsearch · elasticsearch-master-0
💬 JVM heap usage above 85% for 5 minutes
⏰ 2026-08-25 14:32:01
The Mattermost message uses Markdown with an attachment for coloring:
json{
"attachments": [{
"color": "#cc0000",
"text": "🔴 **CRITICAL** · [dashboard](...)\n🖥 `dev`\n📦 `elasticsearch`\n💬 JVM heap..."
}]
}
Colors by severity: #cc0000 (critical), #ff8c00 (warning), #00cc44 (resolved).
Per-environment abot config via patches
The bot token and chat ID differ between environments (different Telegram groups for dev vs production alerts):
yaml# projects/dev/kustomization/hub/patches/abot.yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: abot
namespace: dev
spec:
values:
config:
telegram:
chat_id: "-1009999999999" # dev team chat
mattermost:
webhook_url: "https://chat.example.com/hooks/dev-channel-hook"
Critical production alerts go to a separate higher-priority chat.
abot's own metrics
abot exposes metrics about its notification delivery:
| Metric | Description |
|---|---|
abot_notifications_total{channel,status} |
total notifications sent, by channel and success/failure |
abot_retry_attempts_total |
how many retries were needed |
abot_notification_duration_seconds |
latency of delivery per channel |
These feed into Prometheus via ServiceMonitor. A PrometheusRule can alert on delivery failures:
yaml- alert: AbotNotificationFailed
expr: increase(abot_notifications_total{status="failed"}[15m]) > 3
for: 0m
labels:
severity: warning
annotations:
summary: "abot failed to deliver {{ $value }} notifications in 15m"
Debugging delivery issues
bash# Check abot logs
kubectl logs -n observability deploy/abot
# Test webhook manually
kubectl port-forward -n observability svc/abot 9444:9444
curl -X POST http://localhost:9444/alert \
-H "Content-Type: application/json" \
-d '{"alerts":[{"status":"firing","labels":{"alertname":"TestAlert","severity":"warning"},"annotations":{"summary":"Test"}}]}'
If Telegram is unreachable from the pod, the logs will show connection errors to api.telegram.org. Solution: ensure the proxy is set correctly or check tinyproxy is running on the configured address.
Summary
- abot sits between Alertmanager and notification channels, handling Telegram HTML formatting, Mattermost webhooks, and proxy support in one place
- Single Alertmanager webhook receiver (
url: http://abot.observability.svc.cluster.local:9444/alert) covers all channels - Per-environment bot tokens and chat IDs are injected via Kustomize patches on the HelmRelease values
- abot exposes its own delivery metrics via ServiceMonitor — alert on
abot_notifications_total{status="failed"}to catch silent delivery failures - On-prem clusters route Telegram traffic through tinyproxy; YC clusters with direct internet access skip the proxy via a patch