Building a Notification System with Ntfy and n8n: Push Alerts for Everything
Create a self-hosted notification system using Ntfy for push delivery and n8n for workflow automation. Covers server monitoring alerts, deployment...
The Notification Problem
Modern teams are drowning in notifications from dozens of SaaS tools — Slack, email, PagerDuty, Datadog. Each costs money and adds complexity. What if you could consolidate all alerts into a single self-hosted system?
<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 170" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="170" rx="12" fill="#1a1a2e"/><circle cx="60" cy="85" r="25" fill="#f59e0b" opacity="0.85"/><text x="60" y="82" text-anchor="middle" fill="#1a1a2e" font-size="9" font-family="system-ui" font-weight="bold">Trigger</text><text x="60" y="94" text-anchor="middle" fill="#1a1a2e" font-size="8" font-family="system-ui">webhook</text><polygon points="175,55 210,85 175,115 140,85" fill="#6366f1" opacity="0.85"/><text x="175" y="88" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">If</text><rect x="250" y="35" width="100" height="40" rx="6" fill="#2dd4bf" opacity="0.85"/><text x="300" y="55" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Send Email</text><text x="300" y="67" text-anchor="middle" fill="#1a1a2e" font-size="8" font-family="system-ui">SMTP</text><rect x="250" y="95" width="100" height="40" rx="6" fill="#a855f7" opacity="0.85"/><text x="300" y="115" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Log Event</text><text x="300" y="127" text-anchor="middle" fill="#ffffff" font-size="8" font-family="system-ui">database</text><rect x="400" y="55" width="100" height="40" rx="6" fill="#3b82f6" opacity="0.85"/><text x="450" y="75" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Update CRM</text><text x="450" y="87" text-anchor="middle" fill="#ffffff" font-size="8" font-family="system-ui">API call</text><circle cx="545" cy="75" r="18" fill="none" stroke="#2dd4bf" stroke-width="2"/><text x="545" y="79" text-anchor="middle" fill="#2dd4bf" font-size="9" font-family="system-ui">Done</text><defs><marker id="arrow10" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#e2e8f0"/></marker></defs><line x1="87" y1="85" x2="138" y2="85" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><line x1="210" y1="72" x2="248" y2="55" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><line x1="210" y1="98" x2="248" y2="115" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><line x1="352" y1="55" x2="398" y2="68" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><line x1="352" y1="115" x2="398" y2="82" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><line x1="502" y1="75" x2="525" y2="75" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><text x="225" y="45" text-anchor="middle" fill="#2dd4bf" font-size="8" font-family="system-ui">true</text><text x="225" y="120" text-anchor="middle" fill="#a855f7" font-size="8" font-family="system-ui">false</text></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Workflow automation: triggers, conditions, and actions chain together to eliminate manual processes.</p></div>
Ntfy is a lightweight HTTP-based pub-sub notification service. n8n is a workflow automation platform with 400+ integrations. Together, they create a powerful, self-hosted notification backbone.
Deploying Ntfy
# docker-compose.yml
services:
ntfy:
image: binwiederhier/ntfy:latest
container_name: ntfy
command: serve
volumes:
- ./ntfy/server.yml:/etc/ntfy/server.yml
- ntfy_cache:/var/cache/ntfy
- ntfy_data:/var/lib/ntfy
ports:
- "8093:80"
healthcheck:
test: ["CMD", "wget", "-q", "--tries=1", "http://127.0.0.1:80/v1/health", "-O", "-"]
interval: 30s
timeout: 5s
retries: 3
restart: unless-stopped
volumes:
ntfy_cache:
ntfy_data:Ntfy Server Configuration
# ntfy/server.yml
base-url: "https://notify.example.com"
cache-file: "/var/cache/ntfy/cache.db"
auth-file: "/var/lib/ntfy/auth.db"
auth-default-access: "deny-all"
behind-proxy: true
keepalive-interval: "45s"
upstream-base-url: "https://ntfy.sh"Create Users and Permissions
# Create admin user
docker exec -it ntfy ntfy user add --role=admin admin
# Create topics with access control
docker exec -it ntfy ntfy access admin 'ops-*' rw
docker exec -it ntfy ntfy access admin 'deploy-*' rwSending Notifications
Ntfy's API is beautifully simple — just HTTP POST:
# Simple notification
curl -d "Deployment to production completed successfully" \
https://notify.example.com/ops-deploys
# With title, priority, and tags
curl -H "Title: Disk Space Warning" \
-H "Priority: high" \
-H "Tags: warning,disk" \
-d "Server web1: /var is at 92% capacity" \
https://notify.example.com/ops-alerts
# With actions (clickable buttons)
curl -H "Title: PR Review Requested" \
-H "Actions: view, Open PR, https://git.example.com/pr/42" \
-d "New PR from @dev: Fix authentication timeout" \
https://notify.example.com/dev-prsn8n Integration Workflows
<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 200" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="200" rx="12" fill="#1a1a2e"/><rect x="0" y="0" width="600" height="28" rx="12" fill="#2d2d44"/><rect x="0" y="12" width="600" height="16" fill="#2d2d44"/><circle cx="18" cy="14" r="5" fill="#ef4444"/><circle cx="34" cy="14" r="5" fill="#f59e0b"/><circle cx="50" cy="14" r="5" fill="#2dd4bf"/><text x="300" y="18" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">docker-compose.yml</text><rect x="0" y="28" width="35" height="172" fill="#1e1e32"/><text x="25" y="48" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">1</text><text x="25" y="66" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">2</text><text x="25" y="84" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">3</text><text x="25" y="102" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">4</text><text x="25" y="120" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">5</text><text x="25" y="138" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">6</text><text x="25" y="156" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">7</text><text x="25" y="174" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">8</text><text x="25" y="192" text-anchor="end" fill="#94a3b8" font-size="10" font-family="monospace" opacity="0.5">9</text><text x="45" y="48" fill="#a855f7" font-size="11" font-family="monospace">version</text><text x="100" y="48" fill="#e2e8f0" font-size="11" font-family="monospace">: "3.8"</text><text x="45" y="66" fill="#a855f7" font-size="11" font-family="monospace">services</text><text x="105" y="66" fill="#e2e8f0" font-size="11" font-family="monospace">:</text><text x="55" y="84" fill="#3b82f6" font-size="11" font-family="monospace"> web</text><text x="80" y="84" fill="#e2e8f0" font-size="11" font-family="monospace">:</text><text x="55" y="102" fill="#2dd4bf" font-size="11" font-family="monospace"> image</text><text x="110" y="102" fill="#e2e8f0" font-size="11" font-family="monospace">: nginx:alpine</text><text x="55" y="120" fill="#2dd4bf" font-size="11" font-family="monospace"> ports</text><text x="102" y="120" fill="#e2e8f0" font-size="11" font-family="monospace">:</text><text x="55" y="138" fill="#e2e8f0" font-size="11" font-family="monospace"> - "80:80"</text><text x="55" y="156" fill="#2dd4bf" font-size="11" font-family="monospace"> volumes</text><text x="118" y="156" fill="#e2e8f0" font-size="11" font-family="monospace">:</text><text x="55" y="174" fill="#e2e8f0" font-size="11" font-family="monospace"> - ./html:/usr/share/nginx</text><rect x="365" y="164" width="2" height="14" fill="#6366f1" opacity="0.8"/></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">A well-structured configuration file is the foundation of reproducible infrastructure.</p></div>
Workflow 1: Server Monitoring Alerts
Create an n8n workflow with a Cron trigger running every 5 minutes. Add an SSH node that checks disk usage, an IF node checking if usage exceeds 85 percent, and an HTTP Request node that POSTs to your Ntfy topic with high priority.
The flow looks like: Cron Trigger -> SSH (check disk) -> IF (usage > 85) -> HTTP Request (POST to Ntfy)
Workflow 2: Deployment Notifications
Set up a Webhook trigger in n8n that receives deployment events from your CI/CD pipeline:
# Trigger from CI/CD
curl -X POST https://n8n.example.com/webhook/deploy-notify \
-H "Content-Type: application/json" \
-d '{"service": "api", "version": "2.3.1", "status": "success"}'The n8n workflow receives this, formats it, and sends to Ntfy with appropriate priority and tags.
Workflow 3: Uptime Kuma Integration
When Uptime Kuma detects a service is down, it sends a webhook to n8n. The n8n workflow handles escalation logic — first alert is normal priority. If the service is still down after 5 minutes, send high priority. After 15 minutes, send urgent.
Mobile Setup
Install the Ntfy app on your phone (Android or iOS), subscribe to your topics, and get push notifications for everything — server alerts, deployments, CI/CD results, custom events.
Bash Integration Scripts
#!/bin/bash
# notify.sh - Universal notification helper
NTFY_URL="https://notify.example.com"
NTFY_TOKEN="tk_your_token_here"
notify() {
local topic="\$1"
local title="\$2"
local message="\$3"
local priority="$4"
curl -s -H "Title: \$title" \
-H "Priority: \$priority" \
-H "Authorization: Bearer \$NTFY_TOKEN" \
-d "\$message" \
"\$NTFY_URL/\$topic"
}
# Usage examples
notify "ops-deploys" "Build Complete" "Frontend v2.3.1 deployed to production"
notify "ops-alerts" "High CPU" "web1: CPU at 95% for 10 minutes" "high"Add to your deployment scripts:
# In your deploy.sh
docker compose pull && docker compose up -d
if [ \$? -eq 0 ]; then
notify "ops-deploys" "Deploy Success" "All services updated"
else
notify "ops-alerts" "Deploy FAILED" "Docker compose up failed" "urgent"
fi<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 190" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="190" rx="12" fill="#0d1117"/><rect x="0" y="0" width="600" height="28" rx="12" fill="#1c2333"/><rect x="0" y="12" width="600" height="16" fill="#1c2333"/><circle cx="18" cy="14" r="5" fill="#ef4444"/><circle cx="34" cy="14" r="5" fill="#f59e0b"/><circle cx="50" cy="14" r="5" fill="#2dd4bf"/><text x="300" y="18" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="monospace">Terminal</text><text x="20" y="50" fill="#2dd4bf" font-size="11" font-family="monospace">$</text><text x="35" y="50" fill="#e2e8f0" font-size="11" font-family="monospace">docker compose up -d</text><text x="20" y="70" fill="#94a3b8" font-size="11" font-family="monospace">[+] Running 5/5</text><text x="20" y="88" fill="#2dd4bf" font-size="10" font-family="monospace"> ✓</text><text x="38" y="88" fill="#94a3b8" font-size="10" font-family="monospace">Network app_default Created</text><text x="20" y="106" fill="#2dd4bf" font-size="10" font-family="monospace"> ✓</text><text x="38" y="106" fill="#94a3b8" font-size="10" font-family="monospace">Container web Started</text><text x="20" y="124" fill="#2dd4bf" font-size="10" font-family="monospace"> ✓</text><text x="38" y="124" fill="#94a3b8" font-size="10" font-family="monospace">Container api Started</text><text x="20" y="142" fill="#2dd4bf" font-size="10" font-family="monospace"> ✓</text><text x="38" y="142" fill="#94a3b8" font-size="10" font-family="monospace">Container db Started</text><text x="20" y="165" fill="#2dd4bf" font-size="11" font-family="monospace">$</text><rect x="35" y="155" width="8" height="14" fill="#e2e8f0" opacity="0.7"/></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Docker Compose brings up your entire stack with a single command.</p></div>
Cost Comparison
|----------|-------------|-------------|
At TechSaaS, Ntfy handles all our operational notifications — from deployment alerts to backup verification to security events. Combined with n8n for workflow logic, we have a notification system that rivals PagerDuty at zero cost.
Want to set up self-hosted notifications? Contact [email protected].
Need help with tutorials?
TechSaaS provides expert consulting and managed services for cloud infrastructure, DevOps, and AI/ML operations.