notify-hub
Sends messages to Discord channels via webhook for community or team notifications.
Sends email notifications via Gmail SMTP using app passwords.
Sends push notifications to ntfy.sh topics for real-time alerts.
Sends email notifications via SendGrid SMTP for reliable email delivery.
Sends messages to Slack channels via incoming webhook for team collaboration.
Sends messages via Telegram bot for instant notifications.
Sends WhatsApp messages via CallMeBot API for personal or mobile alerts.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@notify-hubsend a test notification to all channels"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
notify-hub
Self-hosted, free, multi-channel notification gateway. POST one message
with a token and it fans out -- asynchronously, via a durable queue -- to
every channel you've enabled: ntfy, Telegram, Email, Slack, Discord,
WhatsApp, or a generic webhook. Built so a Claude Code
hook can push you "task started" / "task finished" / "Claude needs you"
notifications across every project, without ever blocking Claude.
Why
Claude Code (or any long-running script) leaves you watching a terminal for
minutes with no way to know when it's done or needs input. notify-hub is a
tiny, 100% free, self-hosted service you run once (docker compose up) that
turns "send me a push" into a one-line HTTP call, decoupled from delivery by
a Redis-backed queue with retries and per-channel dead-lettering.
Related MCP server: Trello Universal MCP Server
Architecture
client (curl / hook script)
-> POST /notify (Bearer token) [Fastify API]
-> enqueue dispatch job [Redis / BullMQ]
-> dispatch worker resolves channel set
-> one delivery job per channel [Redis / BullMQ]
-> delivery worker sends via the channel's adapter
(ntfy / telegram / email / slack / discord / whatsapp / webhook)API only validates + enqueues; it never sends inline, so a slow/broken channel can't make
/notifyhang.Worker does the actual fan-out and delivery; each channel gets its own job so retries/failures are isolated per channel (one channel down doesn't block the others).
Channels are pluggable: each one implements the same tiny interface (
send(notification)); enabling one is a config toggle + credentials, and adding a brand-new one is a small adapter file (see the genericwebhookadapter as the reference example).
Quickstart
git clone https://github.com/richardfcampos/notify-hub.git && cd notify-hub
./scripts/setup-env.sh # guided setup: prompts for each channel's credentials
# (hidden input), generates your gateway token, writes
# .env with chmod 600. Or do it by hand:
# cp .env.example .env && $EDITOR .env
docker compose up -d --build
curl http://localhost:8080/health
# => {"status":"ok","redis":true}Send a notification:
curl -X POST http://localhost:8080/notify \
-H "Authorization: Bearer <your-token-from-TOKENS>" \
-H "Content-Type: application/json" \
-d '{"title":"notify-hub","message":"hello from notify-hub"}'
# => 202 {"jobId":"..."}POST /notify accepts:
Field | Required | Notes |
| yes | non-empty string |
| no | defaults to |
| no | one of |
| no | string array, passed through to channels that support it (e.g. ntfy) |
| no | subset of your enabled channels to target this send to; omit to use the token's default channels |
| no | free-form object, passed through to channel adapters (e.g. the |
Responses: 202 {jobId} (enqueued) · 400 (invalid body / unknown channel
name) · 401 (missing/unknown token) · 503 (queue unreachable, so the
caller never hangs).
Configuration
All config is env vars (see .env.example for every key).
Key ones:
PORT-- API listen port (compose maps${PORT:-8080}on the host).TOKENS--;-separatedname:token:defaultChannel1,defaultChannel2entries. Example:phone:supersecrettoken:ntfy,telegram;desktop:othertoken:discord.CHANNELS_ENABLED-- comma-separated list of channels to activate, e.g.ntfy,telegram,discord. A channel not listed here is never attempted, even if a request asks for it. A listed channel missing its required credentials makes the service refuse to start, naming the channel and the missing key -- so misconfiguration is caught immediately, not as a silent drop later.
Channels
Each row is the env keys a channel needs once it's in CHANNELS_ENABLED.
Channel | Env keys | Setup notes |
|
| Use |
|
| Create a bot via @BotFather; get your chat id by messaging the bot then hitting |
|
| Any SMTP provider (Gmail app password, SendGrid, etc.) |
|
| Slack app -> Incoming Webhooks -> add to a channel |
|
| Server channel settings -> Integrations -> Webhooks |
|
| Free personal API via CallMeBot -- message their bot to activate, rate-limited |
|
| Reference extensibility adapter: POSTs the full notification JSON to any URL you control (Gotify, a custom listener, etc.) |
Adding a brand-new channel: implement the NotificationChannel interface
(one send() method) in src/channels/adapters/, export a
ChannelRegistryEntry (factory + required env keys), and add one line to
src/channels/channel-registry.ts. No other core changes needed.
Claude Code hook
A zero-dependency hook script pushes "start" / "end" / "needs-input" events
to notify-hub globally, across every Claude Code project. See
clients/claude-code/install.md for the
full setup (settings.json snippet + env vars).
MCP server
notify-hub also ships as an MCP (Model Context Protocol) server over stdio,
exposing three tools -- send_notification, list_channels,
check_gateway_health -- so an agent (Claude Code, Claude Desktop, any MCP
client) can push notifications and check the gateway as tool calls instead
of hand-rolled HTTP. It's a thin client of the already-running gateway (no
direct Redis access). See
clients/mcp/install.md for the full setup
(claude mcp add command + generic mcpServers JSON config).
Admin panel
A local, dark-themed dashboard for managing everything above without hand-
editing .env:
View every channel (ntfy, Telegram, Email, Slack, Discord, WhatsApp, webhook), toggle it on/off, and edit its credentials -- masked by default, revealed with one click.
Manage token profiles (
TOKENS): add/remove, edit the token, and pick each profile's default channels.Save & Apply validates, backs up
.env(timestamped), writes the new file, and runsdocker compose up -d --no-build api worker-- one click, no terminal.Send test per channel posts a real notification and shows the actual delivery outcome (✅ sent, or the real failure reason ❌), not just "enqueued".
Live gateway status (health, redis, active channels) and a tail of recent worker deliveries.
Comes up automatically as part of the stack -- no extra step:
docker compose up -d
# => http://127.0.0.1:8081npm run admin still works as a host-side dev alternative (no Docker
rebuild needed while iterating on the panel itself):
npm run admin
# => admin panel: http://127.0.0.1:8081Reachability: in compose the host-side bind defaults to 0.0.0.0
(like the other services on a typical homelab host), so the panel is
reachable from your other devices -- e.g. over a Tailscale tailnet as
http://<machine>:8081. The panel has no auth and displays secrets,
so anyone who can reach the port can read and rewrite your config: keep
that surface to networks you trust (a WireGuard/Tailscale tailnet is a
good fit; an untrusted LAN is not). Set ADMIN_BIND=127.0.0.1 in .env
to make it localhost-only. The explicit bind template is asserted by
src/admin/compose-invariants.test.ts. The host-side dev mode
(npm run admin) binds 127.0.0.1 by default regardless.
Docker-socket trade-off: the admin service mounts
/var/run/docker.sock so Save & Apply can run
docker compose up -d --no-build api worker against the real stack from
inside the container (it never recreates the admin service itself --
that would kill the container mid-request). This gives the admin
container control of the host's Docker daemon, the same pattern used by
tools like Portainer. Accepted because the panel is a personal tool on a
trusted network -- combined with the reachability note above, treat
"who can open the panel" as "who can administer this Docker host".
Development
npm install
npm run build # tsc -> dist/bin/{api,worker,admin}.js, copies
# src/admin/ui -> dist/admin/ui (static UI assets)
npm run test # full suite -- REQUIRES Docker (spins up redis:7-alpine
# via testcontainers for the BullMQ retry/dead-letter
# integration test); set REDIS_TEST_URL to reuse a
# running Redis instead of a container
npm run test:unit # no-Docker fast subset (src unit tests only)
npm run test:integration # just the Redis-backed queue integration test
npm run dev:api # tsx, no build step
npm run dev:workerVerified
docker compose up -d --build was run end-to-end against a real ntfy.sh
topic:
docker compose ps-- all 3 services (redis,api,worker) up,apihealthcheckhealthy.curl http://localhost:8080/health->200 {"status":"ok","redis":true}.curl -X POST http://localhost:8080/notify -H "Authorization: Bearer <token>" -d '{"title":"notify-hub","message":"smoke test"}'->202 {"jobId":"1"}.Worker log:
{"channel":"ntfy","msg":"sending notification"}followed by{"channel":"ntfy","msg":"notification sent"}.Confirmed on the ntfy side too:
curl "https://ntfy.sh/<topic>/json?poll=1"returned the exact message:{"title":"notify-hub","message":"smoke test", ...}.
If your environment can't reach the public internet (ntfy.sh), point
NTFY_URL at a self-hosted ntfy instance instead and repeat the same smoke
steps.
Contributing
Contributions are welcome — the most useful one is a new channel adapter. Each channel is a small self-contained file implementing one interface:
Add
src/channels/adapters/<name>-channel.tsimplementingNotificationChannel(a singlesend(notification)method) plus itsChannelRegistryEntry(factory + required env keys).Register it with one line in
src/channels/channel-registry.ts.Add unit tests next to it (happy path + error path, using the fakes in
test/helpers/fakes.ts— no real network in tests).npm run test:unitmust pass; open a PR.
Ideas: Gotify, Matrix, Pushover, Signal, Mattermost, Rocket.Chat, SMS gateways. Bug reports and docs fixes are equally appreciated — open an issue.
License
MIT © Richard Campos
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/richardfcampos/notify-hub'
If you have feedback or need assistance with the MCP directory API, please join our Discord server