Your ntfy notifications, at home on your Mac.
A native macOS client for ntfy. It lives in your menu bar, keeps every message in a searchable inbox,
and makes sure the urgent ones never slip by.
| 🔔 Everything ntfy sends | Priorities, emoji tags, Markdown, click links, icons, and attachments with inline image previews and Quick Look |
| 👆 Action buttons | view, http and copy actions, in the app and right in the notification |
| 🚨 Urgent means urgent | High and urgent messages repeat until you acknowledge them. Repeats replace the old banner instead of piling up. |
| ⏰ Snooze & Reminders | Remind me in 15 minutes, in 1 hour, or tomorrow morning, or send a message to any Apple Reminders list |
| 🗂️ Many servers, many topics | ntfy.sh and self-hosted servers side by side, with no auth, username & password, or access token, all stored in the Keychain |
| ✍️ Publish | ⌘N to send: title, Markdown, priority, tags, link, delayed delivery, file attachments |
| 🔐 End-to-end encryption | Per-topic passphrase. Your server only ever stores ciphertext (format below). |
| 🔕 Quiet when you want | Mute a topic for an hour, until tomorrow, or forever; set a minimum priority; global Do Not Disturb |
| 🔎 Searchable history | Every message is kept locally (one week to one year) with full-text search |
| 🔄 Updates itself | Signed, notarized builds with automatic updates via Sparkle |
- Download the latest release and move Pling.app to Applications.
- Open it and follow the setup.
Pling checks for updates automatically. You can also choose Pling › Check for Updates….
Why does Pling keep running? ntfy's instant push to Apple devices goes through a relay that only the official apps can use. Pling holds its own lightweight connection to each server instead: one HTTP stream per server. It reconnects after sleep or network changes and catches up on anything missed, so it starts at login by default.
Pling for iOS shares the Mac app's code: the same inbox, topics, compose, E2E encryption and swipe actions. iOS suspends apps in the background, and ntfy's own push relay only serves the official apps, so background notifications come from pling-relay, a small Go service in relay/ that runs next to your ntfy server:
- In the Apple Developer portal, create an APNs key (Keys › Apple Push Notifications service) and save it as
relay/apns.p8. cd relay && NTFY_PASSWORD=… APNS_KEY_ID=… docker compose up -d. It joins ntfy's Docker network and listens on port 8080.- Expose it over HTTPS (reverse proxy or tunnel) and enter that URL under Push Relay in the server's settings in Pling.
Devices register with their own ntfy credentials. The relay checks read access for each topic with ntfy and doesn't store those credentials. It forwards encrypted messages as ciphertext, and Pling's notification extension decrypts them on the device. Without a relay, messages arrive while Pling is open.
ntfy has no built-in end-to-end encryption, so Pling defines a small, documented format. To set it up, open the topic's Topic Settings (the slider button in the toolbar), enter a passphrase, and share that passphrase with everyone on the topic.
| Pling E2E v1 | |
|---|---|
| Key | PBKDF2-HMAC-SHA256(passphrase, salt "pling-e2e-v1:" + topic, 600 000 iterations) → 32 bytes |
| Cipher | AES-256-GCM, random 96-bit nonce |
| Plaintext | JSON {title, message, tags, priority, click, actions, markdown, attachment: {name, type, sha256}} |
| Body | pling:e2e:v1: + base64url(nonce ‖ ciphertext ‖ tag), unpadded, ≤ 3072 bytes |
| In the clear | Only the title 🔒 Encrypted message and the priority, so urgency still works |
| Attachments | Sealed the same way and uploaded as attachment.bin. The real name and type, and the hex SHA-256 of the sealed blob (optional; checked when present), are inside the encrypted body. |
Test vector: passphrase correct horse, topic alerts → key 3f4eb1b83ee4637da1ef28b964e6bccd1623c37654f4421ad70dbad6a6c735d4.
cd PlingCore && swift build -c release
cp .build/release/ntfy-e2e /usr/local/bin/
export NTFY_USER=me NTFY_PASSWORD=… PLING_E2E_PASSPHRASE=…
ntfy-e2e send --server https://ntfy.example.com --topic alerts \
--title "Door" --priority 4 --tags door "Front door opened"
ntfy-e2e decrypt --topic alerts 'pling:e2e:v1:…'Home Assistant example
shell_command:
pling_secret: >-
NTFY_TOKEN=!secret ntfy_token PLING_E2E_PASSPHRASE=!secret pling_pass
/config/bin/ntfy-e2e send --server https://ntfy.example.com --topic alerts
--title "{{ title }}" --priority {{ priority | default(3) }} "{{ message }}"brew install xcodegen
xcodegen generate
open Pling.xcodeprojThe project signs with the maintainer's Developer ID. For your own build, change DEVELOPMENT_TEAM and CODE_SIGN_IDENTITY in project.yml, or switch to "Sign to Run Locally".
PlingCore/ Swift package: ntfy client, message model, E2E crypto, ntfy-e2e CLI (swift test)
App/ SwiftUI app: menu bar panel, window, onboarding, settings, notifications
iOS/ iOS app entry, settings, push registration; Notify/ is the notification service extension
relay/ pling-relay: ntfy → APNs bridge for the iOS app (go test ./...)
scripts/ release.sh (Mac: notarize, Sparkle, GitHub release), testflight.sh (iOS)
Integration tests run against a real server when PLING_TEST_SERVER, PLING_TEST_USER and PLING_TEST_PASSWORD are set. Keep them in .env.local, which git ignores:
cd PlingCore && set -a && . ../.env.local && set +a && swift test- Priority 4 and 5 ask for time-sensitive delivery, so they can break through Focus. macOS honours that only with the Time Sensitive Notifications entitlement, which needs a provisioning profile. Without it, these arrive as normal notifications, and repeat alerts still apply.
- Emoji short codes come from github/gemoji (MIT). Updates use Sparkle (MIT).
- Pling is an independent project and isn't affiliated with ntfy.
MIT © Justus-Constantin Weidhaus

