ntfy Notifications for AI Agent Approvals
Get a phone buzz for every AI agent action waiting on your review by wiring Impri to ntfy — no Slack workspace, no app store install, just a topic URL your phone subscribes to.
The self-hosted case for ntfy
Say you're running Impri on a home server alongside a small agent that drafts and queues social posts overnight, and you want a phone notification the moment there's a draft to approve — without creating a Slack workspace just for yourself, or trusting a third-party push SaaS with your homelab's traffic. ntfy is a natural fit: it's a lightweight, MIT-licensed pub/sub push service you can either use hosted at ntfy.sh or run yourself in a container next to Impri, and the client is a single Android/iOS app that subscribes to a topic — no account, no API key on the phone side.
Impri treats ntfy as a first-class notification channel type, alongside Slack, Discord, Telegram, and email.
Creating the channel
Channel management requires an admin-scope API key. Point it at either the public ntfy.sh or your own self-hosted server:
curl -X POST https://api.impri.dev/v1/notification-channels \
-H "Authorization: Bearer $IMPRI_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Phone - overnight posting agent",
"type": "ntfy",
"config": {
"url": "https://ntfy.sh",
"topic": "acme-social-agent-a1b2c3"
},
"digest_window_sec": 60
}'Pick a topic name that's hard to guess — ntfy topics are unauthenticated by default, so anything predictable like my-approvals can be subscribed to by a stranger. A random suffix, as above, is enough on the public server; a self-hosted ntfy instance behind auth removes the concern entirely.
Subscribe from your phone: open the ntfy app, add topic acme-social-agent-a1b2c3 on ntfy.sh (or your own server URL), and you're done. Use POST /v1/notification-channels/:id/test to confirm delivery before trusting it overnight.
Where this fits in the agent loop
The overnight posting agent doesn't know or care that ntfy exists — it just pushes actions to Impri and stops. The channel config you created above is what turns that into a phone notification:
import requests, time
IMPRI_KEY = "im_..."
BASE = "https://api.impri.dev"
def queue_post_for_approval(caption: str, image_url: str) -> str:
resp = requests.post(f"{BASE}/v1/actions",
headers={"Authorization": f"Bearer {IMPRI_KEY}"},
json={
"kind": "social.post",
"title": f"Scheduled post: {caption[:60]}",
"preview": {"format": "markdown", "body": f"{caption}\n\n"},
"expires_in": 43200, # 12h — stale by the next posting window otherwise
"editable": ["preview.body"],
})
action = resp.json()
return action["id"]
# agent's overnight loop calls this once per drafted post, then exits;
# a separate cron job polls each action_id the next morning and publishes on approvalEvery call to queue_post_for_approval creates a pending action; within the channel's 60-second digest window, ntfy batches same-window actions into one notification instead of buzzing your phone once per post.
What ntfy does and doesn't guarantee
Impri validates the channel url as http/https and rejects private-IP targets, and the topic field is restricted to /^[A-Za-z0-9_/-]{1,64}$/ to stop path-traversal tricks when the URL is built server-side — but ntfy itself has no built-in approve/reject buttons the way Impri's Slack channel does. A push through ntfy tells you that something needs review; you still open the Impri inbox link in the notification to actually approve, reject, or edit it. If you want tap-to-decide from the notification itself, that's what Telegram approval is for instead.
A channel that fails delivery five times running is auto-disabled — worth knowing if your homelab server reboots and ntfy is briefly unreachable; check Settings → Notifications after an outage rather than assuming silence means nothing happened.
Next step
Full channel config reference — digest windows, all channel types, secret masking — is in notifications. If you're setting this up on your own server rather than Impri Cloud, self-hosting covers the Docker Compose and environment variables. New to the base approval loop entirely? Start with quickstart.