Notifications For Your Server and My Replacement of NTFY with XMPP (including UnifiedPush)

8 April, 2026 2052 words 10m

Getting notifications from your server can be important, because when something happens, you probably want to know about it.

There are many options for self-hosting a service like this. Previously I have used NTFY , but after some controversy surrounding AI use, I found XMPP/Jabber to be a great replacement.

Why I moved away from NTFY

I don’t like AI, aside from ethical concerns, having it write huge chunks of your code directly is just asking for trouble (in my opinion).

So naturally when I heard that NTFY had released a new version that was wholly written by AI, I was disturbed. Immediately I decided to stay on the current version of NTFY that I was already using without upgrading because I don’t want to support this decision, nor do I want huge chunks of code written by AI running on my systems.

This is the biggest release I’ve ever done on the server. It’s 14,997 added lines of code, and 10,202 lines removed […] The code was written by Cursor and Claude, but reviewed and heavily tested over 2-3 weeks by me.

v2.18.0 Release Notes

In my eyes, you could spend 5 months reviewing the code claude has written and I would still have the same “well, I’m not using it then” stance.

The other thing that has been pushing me away since day one of installing NTFY was its design. Not how it looked, but how the software works in principle. I don’t like its overall design. It didn’t feel like it fit into my stack, it’s like it was meant for use via their public instance, not to be self-hosted. They went in an interesting direction for sure, but I think the “this is cool” factor died down pretty quick for me after actually using it.

Why choose XMPP?

I take advantage of UnifiedPush whenever possible for any applications I have on my android phone. UnifiedPush being quite a new technology with not many implementations left me with only one choice: The Conversations app, which worked with XMPP.

Taking the plunge was easy for me, because I had already heard of XMPP and had meant to try it out as a chatting application anyways, so setting it up once for notifications left the possibility for expansion into the default messenger between friends if that was ever wanted. So hosting this one service can provide two use cases, normally a bad thing for modularity, but I think it’s very suitable in this case (because of what XMPP is).

It just felt perfect as the final place (ie not having to swap this software out for a long time) to stop because it is so flexible.

What about matrix?

As I mentioned and as far as I’m aware, there is no “matrix” app that can act as a UnifiedPush distributor on my phone, but I wanted to mention that I’ve hosted a matrix server in the past and it wasn’t too pleasant an experience.

The server is bulky and in general I don’t think it’s anywhere near as mature as XMPP is.

I know software like this moves slow, but Matrix being glacial while still at the “starting” point is not great because it’s not truly stable yet in my eyes, so I don’t think I’d be hosting it anyways.

In the near future, it’s possible Matrix would be a real good option, but for me it wasn’t too appealing at the moment.

A side note is that It would’ve been easier to use Matrix since I have noticed a lot of services support Matrix notifications as first-party providers, but pretty much none have an XMPP integration (not a big deal if you’re okay with the one time setup cost and adding custom alerts where needed).

Quick Preface

As a preface, XMPP is just a messaging protocol (kinda like Matrix if you know of that). That means we don’t just “install xmpp and be done”, we have a platter of software to pick from!

You may see the term “Jabber” a lot, this is just what XMPP was named originally.

This log entry wasn’t supposed to be a [Matrix] vs XMPP comparison, but I’ll leave just a few notes:

  • XMPP was introduced in 1999; Matrix in 2014.
  • XMPP messages are sent over the XMPP protocol; Matrix is based on HTTP.
  • XMPP messages are in XML; Matrix uses JSON.
  • Both services support federation (interacting with users on different servers).
  • XMPP is more battle tested than Matrix, being that it has been around longer and is actively used by many individuals and companies.

Getting an XMPP service

You can view a full list of server software at XMPP.org: https://xmpp.org/software/?category=servers

Currently it seems that you either pick ejabberd or Prosody IM as they are the most popular choices and are most likely to support all use cases you may desire. You can still of course look through the other available options and see if they suit you (and they support plugins you may want, like one for communication over http for notifications from bots).

I initially went with ejabberd, but quickly changed course to Prosody after realizing how much easier it was to configure and use.

ejabberd is a great choice if you are expecting 10-20k people online at any given moment (as I hear), Prosody being better for the average deployment by individuals like me that expect ~5 people max at any time.

Installing Prosody

The documentation was not as easy as I had expected, but it does exist and It can get you through your installation.

I installed as a Docker container , but you can also install on bare metal .

I found a post online and it helped quite a bit as a starting point for me, so instead of a regurgitating it badly, I think you should also check it out: https://gsvd.dev/blog/my-prosody-setup-with-docker-compose (or view it through the Wayback Machine incase it is down).

Reading the docs and using the post I linked above got me on the right track, of course it is only a starting point since everyones setup can be quite different (my biggest headache was trying to use certificates generated by Caddy with Prosody, which is not a supported use case as of now for Caddy very sadly).

Sending XMPP messages through HTTP

There is a community module for Prosody called mod_rest . It starts a REST API (accessible through Prosody’s HTTP(s) ports) that you can use to send messages.

I installed it and set it up as a Component (and used an environment variable named COMP_SECRET_NOT to provide its secret):

Component "notify.example.com" "rest"
	component_secret = ENV_COMP_SECRET_NOT

Something to note is that messages sent to this API won’t work when the Host header isn’t set to the JID of the component (in this example, that would be notify.example.com). If you are using curl you can just add the correct Host header to fix this, however I decided to add a reverse proxy from notify.example.com to Prosody’s local HTTP port, which fixes this issue automatically (You could probably also correct the Host header in your reverse proxy only when it reaches for the /rest endpoint for example).

I used a reverse proxy because some of the services that I wanted to send HTTP messages from (as custom alerts) didn’t allow me to configure the Host header. It just seemed easier.

For authentication you can provide the Authorization header with a base64 encoded string of the format JID:COMPONENT_SECRET. You can run the following to encode it:

echo -n notify.example.com:my_password | base64

Then you just pass the base64 string into the Auth header:

Authorization: Basic SEVMTE8gSEkgSE9XIGFyZSBZT1U/Pz8/YXNk

Sending messages

My examples are in JSON, because that’s what I prefer using when possible, but you can also use XML or provide the parameters in the url path (just refer to: mod_rest documentation ).

So sending a message through http with curl looks like this:

curl https://notify.example.com/rest \
    -H 'Content-Type: application/json' \
    -H "Authorization: Basic SEVMTE8gSEkgSE9XIGFyZSBZT1U/Pz8/YXNk" \
    --data-binary '{
        "body": "Hello from curl",
        "kind": "message",
        "to": "[email protected]",
        "type": "chat"
    }'

Sending messages into a group chat

You can also send messages into a group chat, which is great for grouping services together or giving each service their own chat with you, both allowing for better organization of messages.

For this to work you should have mod_muc AND mod_muc_bot configured on your Prosody server (mod_muc_bot is a community module, so remember to install it!).

Once your server is configued, you can change the type of message to groupchat and change the to to point to a room JID (and since we are going through mod_muc_bot now, we can also provide a nick (nickname) as I do in the example):

curl https://notify.example.com/rest \
    -H 'Content-Type: application/json' \
    -H "Authorization: Basic SEVMTE8gSEkgSE9XIGFyZSBZT1U/Pz8/YXNk" \
    --data-binary '{
        "body": "Hello from curl to za group chat",
        "kind": "message",
        "to": "[email protected]",
        "type": "groupchat",
        "nick": "Jimbob"
    }'

UnifiedPush

As I mentioned near the top of this post, one of the reasons I chose XMPP was so I could use the Conversations app on my phone for UnifiedPush notifications.

You can get Conversations for free on F-Droid or purchase it on Google Play.

Conversations host their own UnifiedPush endpoint you can use, or you can self-host one with your Prosody server.

Self-hosting UnifiedPush support

For this, we need to install and configure another community module: mod_unified_push

Luckily, It is pretty simple. Under our VirtualHost, we enable the module and configure it:

VirtualHost "example.com"

modules_enabled = { "unified_push" }

unified_push_acl = { "example.com" }

http_external_url = "https://example.com/"

I have also made sure to configure http_external_url to my Prosody servers public address, this is vital if you use a reverse proxy to access your service because otherwise the wrong address could be used. For UnifiedPush specifically, the gateway address generated/trying to be used won’t work.

As an example, if you are reverse proxying, it should look something like this (Caddy example):

example.com {
  # Reverse proxying push paths for unified push to prosody's http local address.
  @prosody path /push/* /_matrix/push*
  handle @prosody {
    reverse_proxy 127.0.0.1:5280
  }

  respond "Whatever else you are proxying" 200
}

Setting up Conversations

I don’t need to give you a guide here because UnifiedPush has one on their website , which I followed, so you should too.

All I need to add is when setting up the Unified Distributor in the app, you set the Push Server to your Prosody address (eg: example.com).

Testing

It’s possible it doesn’t work right away (just like it didn’t for me), so you can take advantage of a few tools to try testing/debugging notifications through UnifiedPush.

UP Example

Install the UP Example app and see Related UnifiedPush Docs .

Once in the app, you click the Register button and then the Send notification button. You should see a notification come in if it worked.

The Register button didn’t work for me (didn’t do anything), what fixed it for me was ensuring I had correctly configured the http_external_url property in my Prosody config (as I mentioned above).

SchildiChat / Element

If you use SchildiChat or Element for Matrix on your phone, they have a great UnifiedPush notification testing section.

Matrix notifications however are sent under a different path (/_matrix/push), so you must ensure your reverse proxy (or whatever) configuration is setup to accept and route it property before trying this. In my Caddy example above, you can see that I handle it the same way I handle anything under /push/.

In the app, just go to Settings -> Notifications and press Troubleshoot notifications (after ensuring you have the Push notification provider set to Conversations in the app too).

Result

Hopefully everything worked for you and now you can use your self-hosted UnifiedPush gateway for all of your apps that support it as well as having a nice way for your servers/bots to keep you up to date.

I’m also hoping there aren’t any glaring issues in this log entry, as its been a few days since I setup this whole thing and my memory isn’t much greater than that of a pebble’s (no offence to pebbles).

Since it’s been a few days I can also tell you that I’ve been really happy with this setup, I haven’t had any issues yet and I don’t expect to. Should be smooth sailing from here on out.