Coding Projects

Bark Alternative for iPhone Push Notifications

Bark is a well-built open source way to push to an iPhone. Here is where its URL-based design helps, where it hurts, and when hosted is the better trade.

Red and black halftone illustration: a metal whistle on a leather cord lying on a dark table (TheNotificationApp)

Bark solves a real problem elegantly. It is an open source iOS app plus a small Go server, and sending yourself a notification is a URL:

curl "https://api.day.app/YOUR_KEY/Backup%20failed/db-01%20exited%201"

That is it. No JSON body, no headers, no POST. Anything that can fetch a URL can notify your phone, which includes a lot of embedded and legacy things that struggle with anything more complicated.

This is a comparison with TheNotificationApp, which is ours. Bark wins several rows below and the limitations section is not buried.

The URL design is the whole story

Bark's defining choice is putting everything in the path. It is what makes it wonderfully easy in some places and awkward in others, and both come from the same decision.

Where it helps. A router firmware, a Shortcuts action, a smart plug, an old NAS with a "call this URL" field and nothing else. Anything that can only do a GET can use Bark. That is a genuinely wide net and it is the strongest argument for it.

Where it hurts. URLs leak in ways bodies do not:

  • Your key sits in shell history, in ps output while the command runs, and in any proxy or server log along the way.
  • Message content has to be URL-encoded. A notification body containing a slash, a question mark or a hash will break or truncate unless you encode it correctly, which is exactly the sort of thing that fails on the alert that mattered.
  • URLs have length limits. A long error message is not a good fit for a path segment.

The body-based equivalent avoids all three:

curl -X POST https://thenotification.app/api/sendNotification \
  -H "app_key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Backup failed",
    "body": "db-01 exited 1 at 03:14 with: cannot open /var/db?lock",
    "link": "https://logs.example.com/backup"
  }'

The key is in a header, and the body can contain any punctuation without escaping gymnastics. Bark does support POST with a JSON body in recent versions, which closes most of this gap, but the URL form is what everyone actually uses and what every example shows.

The hosted instance question

Bark's default server is api.day.app, run by the project as a free public service. Most people use it and never think about it again.

Two things worth knowing. It is a free community service with no uptime commitment, which is a reasonable thing to accept for personal alerts and a strange thing to depend on for anything load-bearing. And your notification content passes through it, which matters if your bodies contain customer data or internal hostnames.

The answer to both is to self-host, and Bark makes that genuinely easy: one binary, one container. But then you are back to running a server, a domain and a certificate, and you need something else to tell you when that server stops, which is the recursive problem all self-hosted alerting has.

Side by side

 BarkTheNotificationApp
CostFreeFree tier, then $2.99 a month
PlatformiPhoneiPhone
Send withGET URL, or POSTPOST with JSON
Key locationIn the URL pathIn a header
Self-hostingYes, supported and easyNo
Default serverCommunity-run, freeHosted in Switzerland
Open sourceYesNo
Notification groupingYesNo
Custom soundsYesNo
Archive on deviceYesNo, not stored
MCP connector for AI agentsNoYes

Three things Bark does that we do not

It is free and open source. No billing, and you can read the code and run it yourself. If either of those is a requirement rather than a preference, that settles it and nothing below matters.

Notification grouping, custom sounds, and an archive. Bark has spent effort on the iOS surface: you can group notifications by category, pick a sound per alert, set an icon, and browse history in the app. Ours has four fields and one delivery style. If you want a distinct sound for production alerts, Bark has it and we do not.

Self-hosting. Full control over where messages pass through, which is the honest answer to any privacy concern about a third party. We are hosted only.

What you get for the trade

Nothing to run, and no free community service in the path. Hosted, in Switzerland, with notification content not stored at all. The reasoning is in why Swiss-hosted push notifications matter for developer privacy.

The key is not in the URL. It stays out of shell history, out of ps, and out of proxy logs.

Webhooks with no code. A URL you paste into any service that has a webhook field, which formats whatever JSON arrives into a readable notification. That covers Uptime Kuma, Grafana, Stripe, GitHub and most other things without writing anything. Details in the static webhook docs.

An MCP connector. AI agents can call it as a tool, authorised with Sign in with Apple rather than a key pasted into a config file an agent can read. That is a use case Bark does not target at all, and it is covered in adding a notification tool to any MCP agent.

Migrating, concretely

Most Bark usage is one of three shapes, and each converts in a line.

A shell alias or function. The common case. Before:

bark() {
  curl -s "https://api.day.app/YOUR_KEY/$(jq -rn --arg s "$1" '$s|@uri')/$(jq -rn --arg s "$2" '$s|@uri')"
}

After, with no URL encoding to get wrong:

notify() {
  curl -fsS -X POST https://thenotification.app/api/sendNotification \
    -H "app_key: $TNA_APP_KEY" \
    -H "Content-Type: application/json" \
    -d "$(jq -n --arg t "$1" --arg b "$2" '{title: $t, body: $b}')" >/dev/null
}

An Apple Shortcut. Change the action from Get Contents of URL on a Bark URL to a POST with a JSON body and an app_key header. Slightly more configuration in the Shortcuts UI, and it stops breaking when a notification body contains a slash.

A service with a webhook field. This one gets simpler rather than harder. Bark's URL form does not accept an arbitrary JSON payload from a third party, so people usually put a script in between. Here you paste a webhook URL directly and the incoming payload is formatted for you:

https://thenotification.app/api/webhook/static/?app_key=YOUR_API_KEY&title=Uptime

There is no data to migrate in any of these, because neither system holds state you need. Run both in parallel for a week if you want; nothing conflicts.

The honest limitations

Not open source, and not self-hostable. You are depending on us. If that is the thing you were avoiding, Bark is the better fit and this is not a close call.

It costs money past the free tier. 100 notifications for the lifetime of the account, then $2.99 a month for 1,000. Bark is free. That is a real difference, and for a low-volume personal setup where free is sufficient it is hard to argue with.

Fewer iOS features. No grouping, no custom sounds, no per-notification icons, no on-device archive. Deliberately small, and small means missing things.

iPhone only, same as Bark, so neither of us helps you on Android.

Which to pick

Stay on Bark if: free matters, you want the code, you are already self-hosting it happily, you rely on grouping or custom sounds, or you are sending from devices that can only fetch a URL. Those are all good reasons and most of them are decisive.

Try the other one if: you are using the public api.day.app for alerts that contain real data and would rather they were not passing through a free community service, or you would rather not run another server, or you want the webhook and MCP paths that Bark does not cover.

Switching is one line, and there is no state to migrate because neither system stores anything you need to keep. Field list is in the API reference, and if you are comparing more than these two, the wider survey is in the best push notification APIs for developers.