Seems wrong to edit this in, so I'll make this post for lookers-on to see what creating a reliable push messaging service looked like in the early '10s:
1) Your app needs to register with Apple to get a push token, using the app's push certificate (which you'd have to generate if your app used push messaging). It needs to send that to a server you control, because that's basically your "to" address to send messages. These were not to be treated as permanent, so to do it right you also had to be ready for these to change.
Then, similar story for Android, but all shoddier, less efficient, and worse-engineered, but also higher-level—so, situation normal for Android vs. iOS in (at least) the early '10s :-) So you're looking at two similar but necessarily entirely separate implementations of this sort of functionality, to cover both platforms.
2) You need to store all those tokens your apps are sending you.
3) To send the push, you need to generate a bunch of individual messages for each address. For Apple, you'll be using the app's push cert to authenticate over a raw (I think? Memory's fuzzy) UDP connection, then blindly firing a stream of data at it (no immediate return statuses, it's a one-directional communication channel). For Google, you'll need to manually chunk these into sets of a certain max size and post the chunks one at a time to some Web endpoint. For an app with one million users sending a broadcast notification to all of them, that's one million messages each time you send. Google's system required that you implement exponential backoff, too, if you didn't want to get banned.
4) Oh, if you're sending to multiple apps, you need a way to manage (add, update) the certs (or, for Google, IIRC, an API token of some kind?) for them and to select the correct one for a given push.
5) Google would tell you at send time if an address was bad (IIRC—it has been about a decade since I touched this stuff). For Apple, you had to wait a while after a send, then open a new connection and ask for a list of bad tokens for that app, which it will happily spit at you as another UDP data stream. These will be uninstalls, folks who've turned off push for your app, whatever. You have to remove those from your token list, facing vague threats of service bans if you keep sending to bad addresses for too long.
6) If your scale is non-tiny and you need any amount of delivery guarantees or reliability, it's plain by now that you need a queue of some kind. So if you're over that line but not too far over it, you hack something together in PostgreSQL or what have you, probably make some minor but OK-for-your-needs errors in the implementation, and live with it. If your needs are greater than that, this is where you start looking at shit like AMQP, which solves a ton of your problems while giving you an ongoing maintenance headache. Retries, fanout to multiple workers, aggregating failure data, et c. There's a lot going on there.
7) Do your senders want to schedule pushes for the future? Now you get to implement cron-for-push-messages, including a UI for it.
8) Do your senders want to send individualized messages? Now you need to hook into some kind of datastore that ties those push tokens to other user data so you can fill that stuff in while generating messages. And you need at least a rudimentary templating system.
9) Do your senders want to send messages in response to user actions in the app? Now you need a way to ingest potentially very bursty and high-volume traffic from a ton of clients, and connect those to configurable push-send triggers. Probably this'll be another thing you need to route through your queuing system, if you don't want to badly over-spend on servers while not having great reliability under load.
10) Push history and stats reporting is probably gonna be needed, at some point, by someone.
This list just keeps going, really.