Skip to content

Grafana SMTP & certificate alerting

The template ships certificate alert rules (in k8s-common/grafana/default-values.yaml) that email the cluster’s operators when a certificate stops renewing. Grafana sends that mail through Postmark, and the credential, sender, and recipient list come from a grafana-smtp Secret in the grafana namespace.

Until this Secret exists, Grafana still runs and the rules still evaluate — they just can’t deliver. Creating it turns delivery on.

Why this is a Secret, and why Grafana rather than Alertmanager

Postmark’s server token is both the SMTP username and the password. Prometheus Alertmanager can read the SMTP password from a file but has no way to read the username from one, so on a public GitOps repo the token would end up committed in plaintext. Grafana reads both from a Secret as environment variables, exposing nothing — which is why alerting lives in Grafana here.

The recipient list lives in the Secret too: it’s usually personal addresses, and this keeps them out of a public repo.

Keys

Key Value
user Postmark server token
password the same Postmark server token
from-address a verified Postmark sender for this cluster (e.g. alerts@example.org)
from-name the sender name shown in alert emails — name the cluster so its origin is obvious at a glance, e.g. CodeForPhilly — Live
recipients semicolon-separated list, e.g. a@example.org;b@example.org

The from-address must be a verified sender signature (or under a verified domain) in the Postmark server, or Postmark rejects the mail. Set from-name distinctly per cluster — when several clusters share one Postmark server and email the same people, it’s the only thing in the inbox that says which cluster fired.

Create it

Sealed into the cluster repo (recommended — survives a cluster rebuild):

TOKEN='<postmark-server-token>'
kubectl create secret generic grafana-smtp \
    --namespace grafana \
    --dry-run=client -o json \
    --from-literal=user="$TOKEN" \
    --from-literal=password="$TOKEN" \
    --from-literal=from-address='alerts@example.org' \
    --from-literal=from-name='CodeForPhilly — Live' \
    --from-literal=recipients='a@example.org;b@example.org' \
  | kubeseal --format yaml \
      --controller-namespace sealed-secrets --controller-name sealed-secrets \
  > grafana/grafana-smtp.sealed.yaml

Commit the sealed file. For a quick non-GitOps setup, drop the kubeseal pipe and apply the plain Secret directly instead — but never commit an unsealed one.

Verify delivery

Don’t assume a rendered config delivers. After Grafana rolls out, fire a real test through its own notifier (a rendered-but-undeliverable pipeline is exactly the silent failure the alerts exist to prevent):

# Grafana's admin password lives in the grafana-initial-admin Secret.
# Test the provisioned contact point with its real, env-expanded recipients:
kubectl -n grafana exec deploy/grafana -- \
  wget -qO- --post-data='{"receivers":[{"name":"cluster-alerts-email",
    "grafana_managed_receiver_configs":[{"name":"cluster-alerts-email","type":"email",
    "settings":{"addresses":"<your-address>","singleEmail":false}}]}],
    "alert":{"annotations":{"summary":"alerting pipeline test"}}}' \
  --header='Content-Type: application/json' \
  "http://admin:$ADMIN_PW@localhost:3000/api/alertmanager/grafana/config/api/v1/receivers/test"

A status: "ok" and an email in the inbox means the pipeline works end to end.