Simple Alerts - Get Notified When Things Break
Status: Active
Last Updated: 2026-08-14
Category: Observability - Phase 1: Basic Monitoring
Prerequisites: uptime-kuma-setup, netdata-basics
Time: 2 hours
Tags: alerting, notifications, email, slack, discord, telegram, webhooks, alert-fatigue, oncall
Summary
Monitoring you don't hear about is decoration. This lesson wires real notification channels โ email, Discord/Slack webhooks, and Telegram โ into Uptime Kuma and Netdata, then teaches the harder skill: designing an alert strategy that stays trustworthy. You'll build severity tiers, write your first runbooks, and apply concrete anti-fatigue rules so every notification deserves attention.
๐ฏ What You'll Learn
By the end of this article, you'll be able to:
- โ Configure email (SMTP) alerts end-to-end and verify delivery
- โ Send alerts to Discord and Slack via incoming webhooks
- โ Set up a private Telegram bot for critical pages
- โ Design a severity tier system (page / notify / log)
- โ Apply anti-fatigue rules: symptom-first, deduplication, grouping, maintenance windows
- โ Write minimum-viable runbooks attached to alerts
Table of Contents
- Alert Design Before Alert Plumbing
- Email Alerts via SMTP
- Discord and Slack Webhooks
- Telegram Notifications
- Severity Tiers
- Fighting Alert Fatigue
- Minimum-Viable Runbooks
- Testing Your Alert Paths
- Troubleshooting
- Key Takeaways
- Next Steps
Alert Design Before Alert Plumbing
Plugging in a webhook takes five minutes. Deciding what deserves to interrupt you is the actual work โ do it first.
The Three Questions Every Alert Must Answer
- Is something actually wrong? (not just unusual)
- Does someone need to act now? (vs. tomorrow's ticket)
- What should they do? (the runbook exists)
An alert that fails any question becomes noise, and noise has a compounding cost: every irrelevant ping lowers your response speed to the next real one.
Symptom-First Checklist
Revisit the principle from why-monitor with concrete examples:
| โ Cause alert (noisy) | โ Symptom alert (actionable) |
|---|---|
| CPU > 90% | Checkout endpoint error rate > 5% for 5 min |
| Memory > 80% | Service restarted 3ร in 1 h (OOM loop) |
| Disk I/O high | Backup push monitor missed |
| Nginx worker count high | Homepage returns non-200 for 3 checks |
Keep cause metrics on dashboards; page humans only for symptoms.
Email Alerts via SMTP
The universal channel โ works everywhere, survives phone loss, searchable. Start here even if you add chat channels later.
Pick an SMTP Source
| Option | Notes |
|---|---|
| Your existing mailbox + app password | Fastest; Gmail/Fastmail need app-specific passwords |
| Transactional service (Mailgun, SES free tier) | Best deliverability; matches fogserv.cloud's emailService pattern |
| Self-hosted relay | Full control, deliverability is your problem |
Configure in Uptime Kuma
Settings โ Notifications โ Setup Notification:
| Field | Example |
|---|---|
| Notification type | SMTP |
| Host | smtp.fastmail.com |
| Port | 465 (implicit TLS) or 587 (STARTTLS) |
| Secure | โ match port choice |
| Username / Password | account + app password |
| From | monitor@yourdomain.com |
| To | ops@yourdomain.com |
Click Test, confirm arrival, then save with "Apply on all existing monitors" checked.
Configure in Netdata
Edit /opt/netdata/etc/netdata/health_alarm_notify.conf (path per netdata-basics):
SEND_EMAIL="YES"
EMAIL_SENDER="monitor@yourdomain.com"
EMAIL_SMTP_SERVER="smtp.fastmail.com"
EMAIL_SMTP_PORT="465"
EMAIL_SMTP_USER="monitor@yourdomain.com"
EMAIL_SMTP_PASSWD="<app-password>"
DEFAULT_RECIPIENT_EMAIL="ops@yourdomain.com"
Then restart and test:
sudo systemctl restart netdata
sudo su -s /bin/bash netdata -c \
'/opt/netdata/usr/libexec/netdata/plugins.d/alarm-notify.sh test'
Expect three messages (warning/critical/clear). No mail? See Troubleshooting.
๐ Store SMTP credentials with dotenvx or your secrets manager (kb/sysadmin/secrets) โ never commit them alongside compose files.
Discord and Slack Webhooks
Chat channels shine for team visibility: history is shared, reactions acknowledge ownership, and recovery messages close the loop publicly.
Discord
- Server Settings โ Integrations โ Webhooks โ New Webhook
- Choose channel (
#infra-alerts), copy URL - In Kuma/Netdata paste the URL as the Discord notification target
Recommended extras:
- Custom username:
Uptime Bot/Netdata - Enable rich/embed formatting so status, host, and duration render as cards
- Create a dedicated low-traffic server or category โ mixing infra alerts into your social server guarantees they scroll away unread
Slack
Slack uses Incoming Webhooks apps instead:
- api.slack.com/apps โ Create New App โ Incoming Webhooks โ Activate
- Add Webhook to Workspace โ pick
#infra-alerts - Copy the
https://hooks.slack.com/services/...URL
Both Kuma and Netdata treat Slack and Discord identically once the URL is pasted โ the payload format differs but the tools handle it.
Channel Hygiene Rules
- One channel per environment (
#prod-alerts,#staging-alerts) โ never one firehose - Pin a message describing what belongs in the channel
- Mute the channel on your phone except when on-call: chat = ambient awareness, not paging
Telegram Notifications
For true pocket-pages without paying PagerDuty, Telegram bots are the self-hoster standard โ instant delivery, read receipts, quiet-hours control per device.
Create the Bot (one time)
1. Chat with @BotFather โ /newbot
2. Name it "fogops-bot", get the bot TOKEN (keep secret!)
3. Add the bot to your group (or DM it directly)
4. Visit https://api.telegram.org/bot<TOKEN>/getUpdates
โ find "chat":{"id": -1001234567890 } โ the CHAT ID
Wire It Up
Uptime Kuma: Notification type Telegram โ paste token + chat ID โ Test.
Netdata health_alarm_notify.conf:
SEND_TELEGRAM="YES"
TELEGRAM_BOT_TOKEN="<TOKEN>"
DEFAULT_RECIPIENT_TELEGRAM="-1001234567890"
Why Keep Telegram Alongside Chat
- Device-level notification overrides: Telegram lets you bypass Do-Not-Disturb for this chat only โ exactly the page-vs-notify split most setups lack
- Delivery confirmation (message shows as read)
- Works when Discord is blocked on corporate networks
Severity Tiers
One channel at one volume trains bad habits. Split everything into three tiers:
| Tier | Meaning | Channel | Latency expectation |
|---|---|---|---|
| ๐ด Page | Users impacted or about to be | Telegram (+ call/SMS escalation) | Respond โค15 min |
| ๐ก Notify | Wrong but not user-facing yet; disk at 80% | Discord/email | Same-day look |
| โช Log | Recorded for trends, no human action | Dashboard only | Never interrupts |
Applying Tiers in Practice
In Uptime Kuma, create separate notifications per tier ("Telegram-Pager", "Discord-Notify") and attach selectively per monitor:
Homepage HTTP check โ Telegram-Pager + Discord-Notify
Backup push monitor โ Telegram-Pager (backups are silent death)
Staging site check โ Discord-Notify only
Certificate expiry โ Notify at 30 days, escalate to Page at 7
Internal NAS ping โ Log/Notify (redundant paths exist)
In Netdata, tier by alarm severity using role-based recipients:
role_recipients_email[sysadmin]="ops@yourdomain.com" # warnings
role_recipients_telegram[sysadmin]="-1001234567890" # criticals page
role_recipients_discord[weblog]="https://hooks.slack.com/..." # info to chat
Escalation Without Paid Tooling
True auto-escalation arrives with Alertmanager in Phase 5 (alertmanager-setup). Until then, fake it acceptably:
- Page-tier Telegram message includes "if unacknowledged in 15 min, reply ๐ to take it"
- A second Kuma notification with longer retry delay targets a second chat/channel as poor-man's escalation
- Document the manual path in each runbook
Fighting Alert Fatigue
Fatigue is the terminal disease of monitoring systems: ignored pagers have negative value because they burn trust needed for future true alarms. Prevent it structurally.
Rule 1: Every Alert Has an Owner and a Runbook
No runbook โ downgrade to Notify until written. This single rule kills 80% of noise because writing "what would I even do?" exposes half your alerts as unactionable.
Rule 2: Aggregate Flappy Things
A service restarting every few minutes shouldn't produce 40 messages. Alert on the pattern: "restart count > 3 in 30 min." In Kuma, raise retries; in Prometheus (Phase 2+) use increase() over windows rather than instantaneous triggers.
Rule 3: Deduplicate Related Failures
When the database dies, every dependent service screams simultaneously. Prefer alerting on the root dependency (DB health) and letting dependents show degraded-on-dashboards. Alertmanager's inhibition handles this properly later (alertmanager-setup); manually, order monitors so the DB check is the loudest.
Rule 4: Maintenance Windows
Before planned work, silence notifications for affected monitors (Kuma supports per-monitor disable + scheduled maintenance; Netdata supports silencing alarm types). Post-maintenance, re-enable and verify green. Unsilenced planned work manufactures false pages.
Rule 5: Monthly Alert Review
Calendar reminder: for each alert that fired last month ask โ
fired N times โ required action N times?
yes, all โ keep
sometimes โ tighten threshold / add hysteresis
never โ demote tier or delete
never fired โ still relevant? test it or retire it
Thirty minutes monthly preserves years of pager trust.
Minimum-Viable Runbooks
A runbook is not documentation theater โ it's the difference between a 5-minute fix and a 45-minute panicked archaeology session at 3 AM.
Template (steal this)
## [ALERT-NAME] <one-line what broke>
**Fires when**: <exact condition, e.g. "homepage non-200 ร3 @60s">
**User impact**: <what users see>
**Severity**: Page / Notify
### Diagnose (fastest first)
1. `curl -I https://site` from outside โ confirm scope (all vs one node)
2. `docker ps` โ anything restarting/exited?
3. `df -h && free -m` โ resource exhaustion?
4. Netdata dashboard โ what changed in the last hour?
### Fix
- App down: `docker compose up -d` in ~/apps/<app>, watch logs 2 min
- Disk full: clear docker build cache `docker system prune -f`, then investigate growth
- OOM: identify consumer via Netdata containers view, restart it, open issue
### If that fails
- Reboot the host (last resort, ~2 min downtime): document why afterwards
- Escalate: post in #infra-alerts with steps tried
### After resolution
- Verify alert cleared automatically
- Append 3 lines to the incident log: cause / fix / prevention idea
Linking Runbooks to Alerts
- Kuma: put the runbook URL in the monitor's description field (and keep runbooks in this KB so they're versioned โ e.g.,
kb/runbooks/<alert-name>.md) - Netdata: reference runbooks in alarm definitions'
info:line, which appears in notifications - Target: every ๐ด Page-tier alert resolves to a clickable runbook within two clicks of the notification
Testing Your Alert Paths
Untested alerting fails exactly when it matters. Build a quarterly (monthly is better) drill:
#!/bin/bash
# alert-drill.sh โ deliberately trigger each tier, confirm delivery
set -x
# 1. Page tier: stop a monitored container
docker stop nginx-proxy && sleep 240 && docker start nginx-proxy
# 2. Notify tier: fill a scratch disk past warning threshold
dd if=/dev/zero of=/mnt/scratch/filler bs=1M count=9000
rm /mnt/scratch/filler
# 3. Recovery: confirm CLEAR/recovery messages arrived too
Checklist after each drill:
- Page arrived on phone within expected MTTD (~interval + retries, see uptime-kuma-setup)
- Recovery notification arrived
- Message contained actionable context (which monitor, which host, since when)
- Runbook link worked
Log drills in the KB like any other operational change โ GitOps is Law (kb/workflows).
Troubleshooting
Test notification never arrives (email)
- Wrong port/TLS pairing: 465 = implicit TLS ("SSL" toggle ON), 587 = STARTTLS ("TLS" toggle in some UIs)
- Provider rejects login: use app password, not account password
- Check spam folder before assuming failure
- From Kuma container: outbound port 25/465/587 may be blocked by host firewall โ test
docker exec uptime-kuma timeout 5 bash -c 'cat < /dev/tcp/smtp.host/465'
Discord/Slack webhook posts nothing
- URL copied with whitespace/newline โ re-copy
- Slack webhook deleted after app reinstall โ regenerate
- Rate limits: bursts of many failing monitors can hit 1 msg/sec-ish limits; enable aggregation/dedup settings where offered
Telegram bot silent
- Token revoked (regenerated via BotFather invalidates old ones)
- Wrong chat ID sign/format: groups need the
-100โฆsupergroup form; re-fetch via getUpdates after messaging the bot - Bot removed from the group
Alerts arrive but hours late
- Phone-level battery optimization throttling background sync โ exempt the chat app
- SMTP queueing on a self-hosted relay โ check relay logs
- Kuma instance itself starved (check its own host in Netdata โ monitor the monitor)
Everything alerts at once during a network blip
That's deduplication failure (Rule 3). Short term: raise retries. Long term: this is the strongest argument for graduating to Prometheus + Alertmanager inhibition in Phase 2/5.
Key Takeaways
- Design before plumbing: symptom alerts only, three questions per alert, owner + runbook mandatory.
- Email is the backbone, Discord/Slack give shared visibility, Telegram gives real paging with DND-bypass.
- Three tiers โ Page / Notify / Log โ with different channels and response expectations each.
- Fatigue is structural: aggregate flaps, dedupe cascades, use maintenance windows, review monthly.
- Every page links to a runbook; unrunbooked alerts get demoted.
- Drill the paths quarterly โ an unfired alert path is a rumor, not a capability.
๐ Related
- Next phase: prometheus-introduction โ from simple alerts to a real metrics database
- Also in Phase 1: why-monitor, uptime-kuma-setup, netdata-basics
- Later depth: alertmanager-setup (proper routing/inhibition), on-call-workflows (incident response)
- Course overview: README
- Related KB sections:
- kb/security/fail2ban-setup โ security events deserve their own tier
- kb/sysadmin/secrets โ storing webhook tokens and SMTP credentials