ntfy is open source, free to self-host and the easiest way to get a one-way alert from a script. Pushover is a paid, polished, hosted service whose emergency priority keeps alerting until you acknowledge it. Slack incoming webhooks cost nothing extra if your team already lives in Slack, but they only post. None of the three lets an agent wait for your answer without plumbing you build and run yourself.
That last point is the one most comparisons skip. If all you need is "the build finished", any of these works. If you need "the agent is about to drop a table, yes or no?", read the section on replies before you pick.
At a glance
Every number below comes from the vendor's own docs or pricing page, checked in October 2026. AlertMCP is included as an MCP-native option, with its gaps shown too.
| ntfy | Pushover | Slack incoming webhook | AlertMCP | |
|---|---|---|---|---|
| Price | Free on ntfy.sh; Supporter $5, Pro $10, Business $20 a month | $4.99 one-time per platform after a 30-day trial; Teams $5 per user a month | No per-message fee; part of a Slack app in your workspace | Free up to 1,000 alerts a month and 5 devices; Team $8 per seat a month |
| Free sending limit | 250 messages a day on ntfy.sh | 10,000 messages a month per account (25,000 for Teams) | 1 message per second per channel, short bursts allowed | 1,000 alerts a month |
| Message size | 4,096 bytes; longer becomes an attachment | 1,024 characters, title 250 | Not covered here | Title 200, body 2,000 characters |
| Priority levels | 5 (min to max/urgent) | 5 (-2 to 2); 2 repeats until acknowledged | None | 4 (silent, normal, high, critical) |
| Buttons | Up to 3 actions (open URL, HTTP request, copy; broadcast on Android) | None; one supplementary URL | Needs an app with Interactivity and a Request URL | 2 to 4 answer buttons |
| Typed reply | No | No | No | Yes (ask_text) |
| Sender can wait for the answer | Only via your own endpoint | Acknowledgement only, via receipt or callback | No | Yes (get_answer) |
| Self-hosting | Yes, free | No | No | Coming soon |
| License | Apache 2.0 and GPLv2 (dual) | Closed source | Closed source | MIT self-hosted server coming soon |
| Apps | Android, iOS, web app | iOS, Android, desktop | Slack's own apps | Android, iOS (TestFlight beta), macOS, Windows, Linux, web, CLI |
ntfy
ntfy is a pub-sub notification service over plain HTTP. You pick a topic name, subscribe to it in the app, and anything that can make an HTTP request can publish to it. There's no account or API key for the basic case:
curl \
-H "Title: Deploy finished" \
-H "Priority: high" \
-d "billing-api is live on staging. 482 tests passed." \
https://ntfy.sh/your-long-random-topic
Without an account, the topic name is the only thing protecting the channel. The ntfy.sh homepage says so itself: the topic is essentially a password. Use a long random name, or create an access token (ntfy token or the web app's Account page) and send it as Authorization: Bearer tk_....
What ntfy does well:
- Priorities. Five levels, from 1 (min) to 5 (max/urgent), set with the
Priorityheader by number or name. - Action buttons. Up to three per message. A
viewaction opens a URL, anhttpaction sends an HTTP request when tapped (POST by default), andcopycopies text.broadcastis Android only. - Self-hosting. The server is a single open-source binary, dual licensed under Apache 2.0 and GPLv2. Run it on a home server and you set your own limits.
- Price. The free ntfy.sh tier allows 250 messages a day, 2 MB attachments and 200 MB of daily bandwidth. Paid plans are $5, $10 and $20 a month for 2,500, 20,000 and 50,000 daily messages. The page currently shows those as discounts from $6, $12 and $25, so check before you budget.
Where it falls short:
- iOS. In March 2026 the maintainer opened an iOS improvement plan that says the iOS app "is significantly behind the Android app in both reliability and features". The issue is still open. On Android, ntfy is excellent.
- Self-hosted iOS push has to relay through ntfy.sh: the known-issues page tells you to set
upstream-base-urltohttps://ntfy.sh. Self-hosting doesn't fully take ntfy.sh out of the path for iPhone users. - No typed replies. An
httpbutton can call a URL you choose, but the user can't type anything back.
Pushover
Pushover is a hosted push service that has been around for years and works the same on every platform. You pay $4.99 once per platform (iOS, Android or desktop) after a 30-day trial. Teams cost $5 per user per month.
Each account can send 10,000 messages a month, shared across all of its applications, and Teams get 25,000. Past the limit, the API returns HTTP 429. Messages are capped at 1,024 characters with a 250-character title (API docs).
The feature that sets Pushover apart is emergency priority. Priority 2 repeats the alert until you acknowledge it. You must set retry (at least 30 seconds) and expire (at most 10,800 seconds, three hours), and retries are capped at 50:
curl -s \
--form-string "token=$PUSHOVER_APP_TOKEN" \
--form-string "user=$PUSHOVER_USER_KEY" \
--form-string "title=Prod deploy failed" \
--form-string "message=billing-api: migration 0042 timed out." \
--form-string "priority=2" \
--form-string "retry=60" \
--form-string "expire=1800" \
https://api.pushover.net/1/messages.json
The response includes a receipt. Poll https://api.pushover.net/1/receipts/RECEIPT.json?token=APP_TOKEN (no faster than once every five seconds) to see acknowledged, acknowledged_at and acknowledged_by_device, or pass a callback URL and Pushover will POST to it when someone acknowledges.
That's the closest Pushover gets to two-way: you learn that someone saw the alert, and on which device, but not what they think. There are no action buttons, only a supplementary URL under the message. Pushover isn't open source and can't be self-hosted.
For on-call alerts that must wake someone up, nothing else in this comparison does what emergency priority does out of the box.
Slack, Discord and Telegram
Chat apps are where many teams already look, so a webhook into a channel is often the right first step.
Slack incoming webhooks
Create a Slack app, turn on Incoming Webhooks, click Add New Webhook to Workspace and pick a channel. You get a URL that accepts JSON (Slack docs):
curl -X POST -H "Content-type: application/json" \
--data '{"text":"Deploy finished: billing-api is live on staging."}' \
"$SLACK_WEBHOOK_URL"
The limits are worth knowing. You can post about one message per second per channel, with short bursts allowed. Incoming webhooks can't delete a message after posting it. And while a webhook message can contain buttons, receiving the click needs a Slack app with Interactivity turned on and a Request URL your app serves, which must acknowledge each click within 3 seconds. That's a web service to build and keep running, not a curl command.
There are no priority levels. Whether a message buzzes your phone depends on your Slack notification settings for that channel.
Discord webhooks
Discord's version is just as simple:
curl -H "Content-Type: application/json" \
-d '{"content":"Deploy finished: billing-api is live on staging."}' \
"$DISCORD_WEBHOOK_URL"
The webhook docs say non-application-owned webhooks can't send interactive components, so a plain webhook has no buttons at all.
Telegram bots
A Telegram bot is the most capable free option for answers, as long as you run the bot yourself. sendMessage accepts an inline keyboard, and each button carries 1 to 64 bytes of callback_data:
curl -s "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" \
-H "Content-Type: application/json" \
-d '{
"chat_id": "'"$TELEGRAM_CHAT_ID"'",
"text": "Run migration 0042 on staging?",
"reply_markup": {"inline_keyboard": [[
{"text": "Not yet", "callback_data": "m0042:no"},
{"text": "Run it", "callback_data": "m0042:yes"}
]]}
}'
Your code then reads the tap from getUpdates (long polling) or a webhook, and calls answerCallbackQuery to clear the spinner. ForceReply asks for a typed answer. The Bot API covers all of it. Keep to about one message per second per chat.
Wiring them to coding agents
Every major coding agent can run a command when it finishes, and that command can be one of the curl lines above.
- Claude Code has
NotificationandStophooks in its settings. Our Claude Code notifications guide covers the setup. - Cursor runs a
stophook fromhooks.jsonwhen the agent loop ends. The Cursor notifications guide has a script that only pings for runs over two minutes. - Codex has a
notifysetting in~/.codex/config.toml, for examplenotify = ["python3", "/path/to/notify.py"]. It runs onagent-turn-completeand passes a JSON argument that includeslast-assistant-messageandcwd. Codex ignores it in a project-level.codex/config.toml.
There are also community MCP servers for ntfy, such as cyanheads/ntfy-mcp-server (Apache 2.0) and gitmotion/ntfy-me-mcp (GPL-3.0). They let the agent decide when to publish, which is handy, but they're built for sending, not for waiting on your reply.
Hooks have the same limit from the other side: they fire after the agent has stopped, so the agent can't use one to ask you something and carry on.
When the agent needs an answer
A ping says "come look". An approval says "I'm stopped until you decide". The second needs three things a webhook doesn't have: a way to answer from the notification, a path for the answer back to the sender, and a sender that can wait.
Building it yourself
You can build this from the parts above. With ntfy alone, the buttons publish to a second topic and the script polls that topic:
REPLY=replies-your-long-random-suffix
start=$(date +%s)
curl -s \
-H "Title: Run migration 0042 on staging?" \
-H "Actions: http, Not yet, https://ntfy.sh/$REPLY, body=no, clear=true; http, Run it, https://ntfy.sh/$REPLY, body=yes, clear=true" \
-d "The agent is waiting. No answer in 10 minutes means no." \
https://ntfy.sh/your-long-random-topic
for i in $(seq 60); do
answer=$(curl -s "https://ntfy.sh/$REPLY/json?poll=1&since=$start" \
| jq -r 'select(.event == "message") | .message' | tail -n 1)
[ -n "$answer" ] && break
sleep 10
done
echo "${answer:-no}"
It works, and it's worth seeing what you now maintain. The agent has to run this as a shell command that blocks for up to ten minutes, which many agents time out or flag. Anyone who guesses the reply topic can answer. Nobody can type a reason. If two questions are open at once, the answers share a topic. The Telegram version is sturdier, but you're running a bot process.
Using the vendor's app
If you only use one agent, check whether its vendor already solved it. Claude Code has Remote Control, Cursor has an iOS app, and Codex works in the ChatGPT app. They're the native way to deal with the agent's own permission prompts from a phone, which nothing else here can do. Our Claude Code Remote Control guide covers the trade-offs for that one.
Using an MCP ask-and-wait server
The third route is to give the agent a tool for asking. With MCP, the question is a tool call and the answer comes back as the tool's result, so the waiting happens inside the agent loop rather than in a script you wrote.
AlertMCP works this way. Once it's connected:
asksends a question of up to 200 characters with two to four buttons, safest option first.ask_textasks for a typed reply.get_answerwaits up to 50 seconds per call (25 by default) and returns answered, pending or timed out. On pending, the agent calls it again.- Each workspace sets a deadline from 5 minutes to 24 hours. When it passes, the agent is told nobody answered, or continues with the first (safest) option, or the question escalates to team admins. You pick which.
notifycovers the one-way case, with four priorities and per-topic rate limits and quiet hours so the agent can't flood you.
To be clear about the limits: it's agent-initiated. The model calls ask because your CLAUDE.md, AGENTS.md or rules tell it to, so it doesn't intercept Claude Code's, Cursor's or Codex's own approval dialogs. Alerts are encrypted with HTTPS in transit and AES-256-GCM at rest, but the relay reads an alert in order to deliver it, so the encryption isn't end to end, and pushes go through Firebase and Apple. The self-hosted MIT server is coming soon, not available today.
It isn't the only option. Pushary offers ask_user and wait_for_answer tools, and community servers do the same over Telegram or Discord. The question of when an agent should ask at all is covered in our guide to human-in-the-loop AI agents.
Which one should you pick?
- You want free, open source and self-hosted: ntfy. Nothing else here runs on your own box. It's the best fit on Android, and it's the easiest to script.
- Someone must be woken up: Pushover. Emergency priority with retries and receipts is mature and works the same everywhere. $4.99 once is cheap for that.
- Your team already watches a channel: Slack or Discord webhooks. Good for shared visibility ("deploy finished", "nightly eval failed"), weak for urgent or personal alerts.
- You want buttons for free and don't mind running a bot: Telegram. It's the most capable DIY approval channel.
- The agent needs your answer to continue: an MCP ask-and-wait server, or your agent vendor's own app if you only use one agent. If you need self-hosting today, build the ntfy or Telegram loop above instead, because AlertMCP's self-hosted version isn't out yet.
Plenty of setups use two of these. ntfy or Pushover handles "it finished", and an MCP server handles "may I?". Pricing for the AlertMCP side is on the pricing section.
Frequently asked questions
- Is ntfy free?
- Yes. The hosted ntfy.sh service is free without an account, up to 250 messages a day. Paid tiers are Supporter at $5 a month, Pro at $10 and Business at $20, and the open-source server is free to self-host.
- How much does Pushover cost?
- Pushover is a one-time $4.99 purchase per platform (iOS, Android or desktop) after a 30-day free trial. Each account can send 10,000 messages a month. Pushover for Teams costs $5 per user per month.
- Can ntfy or Pushover send a reply back to my script?
- Not as a typed reply. ntfy action buttons can fire a preset HTTP request that you then have to receive somewhere. Pushover only tells you whether an emergency-priority message was acknowledged, through a receipt you poll or a callback URL.
- Is ntfy reliable on iPhone?
- The ntfy maintainer wrote in March 2026 that the iOS app is significantly behind the Android app in reliability and features, and published a plan to fix it. Self-hosted servers also have to relay through ntfy.sh to deliver iOS push.
- What should I use if my AI agent has to wait for my approval?
- You need a two-way channel the agent can wait on. The options are a Telegram bot with inline buttons and polling, your agent vendor's own mobile app, or an MCP server with ask-and-wait tools such as AlertMCP or Pushary.