HomeGrafana Tools › SMS & voice alerts

Grafana SMS and Voice Call Alerts

Grafana has no built-in SMS notifier — email, Slack and PagerDuty, but nothing that reaches a phone directly. This is a small webhook adapter that fixes that: Grafana posts an alert to it, and it sends an SMS or places a text-to-speech voice call through an SMSEagle gateway. One contact point handles both, and each rule decides which it gets.

Watch the setup

The whole thing wired up end to end, from container to a phone ringing:

How it works

Grafana Alerting webhook contact point alert-webhook :9099 · reads 2 labels SMSEagle APIv2 SMS Voice call
A single webhook contact point. The alert's own labels decide whether it becomes a text or a phone call.

Two labels do all the work

You configure one contact point. Which channel an alert uses, and who it reaches, are set per rule with labels — so on-call routing lives in the rule rather than in a pile of near-identical contact points.

LabelValueResult
smseagle_channelcall, voice, tts Places a voice call that reads the alert aloud.
smseagle_channelsms, anything else, or absent Sends an SMS — the default.
smseagle_to+37120000000,+37120000001 Overrides recipients for this alert only.
smseagle_toabsent Falls back to the defaults in .env.

A typo fails safe. voice and tts are aliases for call; anything unrecognised — including cal — falls through to SMS, so a slip sends a text rather than ringing someone at 3am.

In practice: put smseagle_channel: call on the handful of rules that genuinely warrant waking someone, and leave it off everywhere else. A phone call is much harder to sleep through than a text, which is exactly why it should be rare.

Before you start: will Grafana reach the device?

This is a device-bound path — Grafana's alerting engine must be able to reach the SMSEagle on your network. The clean fit is self-hosted Grafana on the same LAN as the gateway: no inbound exposure, no tunnel. Grafana Cloud cannot reach a private LAN address, so if you are on Cloud, use SMSEagle's Email2SMS poller instead.

Quick start

First, make sure the SMSEagle API token carries the Send SMS permission — and Send calls too if you want voice. For calls, note a TTS voice model id under Calls → TTS Voice models.

git clone https://github.com/dmitrylambert/grafana-monitoring.git
cd grafana-monitoring/smseagle-alert-webhook
cp .env.example .env      # device IP, API token, default recipients
docker compose up -d --build
curl http://localhost:9099/healthz    # -> ok

Leave SMSEAGLE_TEST_MODE=true while you wire things up: SMSEagle validates every request but delivers nothing, so you can test the whole path without sending real messages. Flip it to false and rebuild when you are satisfied.

After editing .env or app.py, re-run docker compose up -d --build. A plain restart keeps the old image, which is the most common reason a change appears to do nothing.

Wiring up Grafana

In Alerting → Contact points → Add contact point, create a Webhook integration named SMSEagle pointing at the adapter. The URL depends on where Grafana runs:

Grafana runs…Contact point URL
In the same Docker networkhttp://smseagle-alert-webhook:9099/
On the same host, port publishedhttp://host.docker.internal:9099/
Elsewhere on the LANhttp://<adapter-host-ip>:9099/

Then route alerts to it under Notification policies, and add smseagle_channel: call to whichever rules should ring a phone. Provisioning files for the contact point, policy and a sample rule are in examples/grafana-provisioning/ if you would rather keep it in version control.

What the message sounds like

The body is a status word followed by your alert's annotation, verbatim — no grouped-label preamble, no firing counts:

PROBLEM.  SMS outbox > 20 on smseagle-gateway
RESOLVED. SMS outbox > 20 on smseagle-gateway

It takes the alert's message, summary or description annotation, whichever is present first, so what you write on the rule is exactly what gets read aloud. That matters far more for voice than for SMS: hearing a machine recite label pairs and firing counts at 3am is genuinely unpleasant.

Grafana notifies on both firing and resolved, so by default a cleared alert produces a second message. Tick Disable resolved message on the contact point to stop that — it applies to both channels.

Troubleshooting

Nothing arrives, and Grafana runs in Docker

The contact point URL has to be reachable from inside the Grafana container. There, http://localhost:9099 is Grafana itself, not the adapter. Use the container name, or host.docker.internal with extra_hosts: ["host.docker.internal:host-gateway"] on the Grafana service. localhost only works when Grafana runs natively.

The contact point's Test button always sends the default SMS

Grafana's test payload is synthetic and carries none of your rule's labels or annotations, so smseagle_channel, smseagle_to and your message text do not apply. Test with a real firing rule and watch docker logs -f smseagle-alert-webhook.

Labels cannot be edited on a rule

Provisioned rules are read-only in the UI. Create the rule in the UI instead, or edit the provisioning YAML.

Alerting that reaches the right person?

Getting alerts out is the easy half — routing them so the right person is woken and nobody else is, is the part that takes judgement. Tell me what your on-call looks like and I will reply with how I would wire it.