Send iOS push notifications from the command line with pu.sh
Sending iOS push notifications from the command line with a single bash script located on GitHub here using no external dependencies or services.
I’ve been using push notifications in most of the apps I build for a while now. On iOS, they are the centerpiece of the remote notifications feature. They are fast, secure and decrease the required effort developers need to make in order to efficiently propagate information towards their apps and users.
Moreover, these days you can display rich and comprehensive content within them. I have written an article about that here that you might enjoy reading while finding it useful for your own app development.
So while I’m happy with the technology, there is one area where I’ve consistently found myself wanting and that’s testing them; by that, I mean being able to send them to a particular device that has an app of mine installed on it consistently and repetitively over many many times so I can test, tweak and rewrite its text and payload.
Testing push notifications on iOS
Usually, you might have your own server setup or maybe you use one of the many push notification services that already exist. All this takes a lot of time and effort to get up and running. Ideally, you would probably like to start sending notifications just with a mac, an iOS device and zero in cash (excluding the money you’ve paid for your mac and device of course).
If you’ve done some research online you’ve probably come across NWPusher which works great but only works with certificate-based authentication for certificates and keys that it reads directly from the keychain. It’s also distributed as a Mac app you need to install so it only works on that platform. You will also have to generate these certificates yourself every year and this is a multistep process each time.
The shell script solution
Instead of certificate-based notifications, I used token-based instead, which are much easier to set up and manage. Also, instead of creating a graphical app to send notifications with, I wrote a bash shell script to do so. It is called pu.sh
and is located on GitHub here and is fully documented there also. Meanwhile, you can also bear with me below for the more comprehensive walkthrough.
I would also like to note that with the shell script solution transparency is assured. You know in advance exactly what the script does with the information you provide, which is that it only sends a payload over to the APNs servers.
Gathering all the prerequisites
The following two sections are a condensed version of the information found in the official docs here. For our solution, we will be using token based push notifications instead of certificate-based ones.
Token-Based Connection to APNs
Token-based authentication is faster than certificate-based communication because it does not require APNs to look up the certificate that is related to your server. You can use the same token for multiple provider servers and all your companies apps can use one token. Keep in mind that you must update and your tokens at least once per hour using your signing key. We will go through how to update your tokens shortly. Tokens also last for as long as you like; until you revoke them that is whereas certificates have to be reissued and redistributed every year.
Obtain an encryption key and a key ID
You need an APNs authentication token signing key to generate the tokens used by your server in order to send push notifications. You request this key from your developer account on developer.apple.com in the Keys ➙ All section as shown below.
After continuing to the next step you get:
- A 10-character string with the key ID. Keep this on hand you will need it. If you forget it it is still available in the developer portal.
- A signing key as a
.p8
text file. Keep this somewhere safe. For instance, don’t keep it in your source code repository. If you lose it it’s gone forever and you will have to revoke it and regenerate it. If this key is compromised in any way it can be used to send push notifications to your apps 🙀so if you suspect this has happened, revoke it and request a new one.
You can already see how much simpler this process is than creating keys, certificates etc.
Obtaining a device token
Below are the methods of your apps application delegate, with some short explanation comments, you need to obtain a device token.
You’ve probably noticed that we don’t ask for the user's permission in the above code. In this article, we’ll be using silent notifications by setting the content-available
key to a value of 1
in the notification payload. For your own implementations though, you can omit this flag of course, and ask the user for permission to send push notifications to them. Now you are free to send any type of payloads you want. There is plenty of info here to assist you.
Let’s Create and Encrypt our token
Install OpenSSL
You can skip to the next section if you have already installed OpenSSL.
In order to generate our token, we need to have OpenSSL installed on our machine. If you are on a mac (if you are building iOS apps you probably are), do the following:
- Open a terminal
- Install homebrew:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- Type
brew install openssl
Done!
Generate a token
Below is the bash script that generates a token. You will have to supply your team id (found in your developer account), key id (found in the section we created a key earlier) and the p8 file you created in the previous step and you have kept in a safe place.
To execute the script place it in a .sh
file (e.g. script.sh) and within a terminal within the directory the script resides type ./script.sh
.
Your output will look something similar to this (three strings connected by two dots):
eyAiYWxnFCIiB9.eyAiaXNz1OTMwOCB9.MEUCIQDRXq8MwIQP5zeSnpwlQ
This a JSON web token or JWT in short. You can find out more about them at https://jwt.io/ along with a plethora of signing and verification libraries in many languages.
Send a push notification to a device
Now we have all we need to send the push notification; that is a signed token and a device identifier. In the script below you will just have to provide the bundle id of your app and the device token you acquired earlier from the Xcode console.
Note: Development vs Production
Replace the ENDPOINT
text above with the corresponding URL in the commented out section at the top of the script. Use development when running your app directly from Xcode onto a device, and production in every other case which is for Testflight, Adhoc, Enterprise or for the Appstore.
Receive a push notification
If you have a physical device (not a simulator, push notifications don’t work in the simulator) plugged in or wirelessly connected to Xcode, running this script will send a notification to that device. You can verify that below:
You just received a push notification 🚀
Note: Rich push notifications
To send rich push notifications import the UserNotifications
framework and add the UNUserNotificationCenterDelegate
in AppDelegate.swift. Then in didFinishLaunchingWithOptions
somewhere add this.
You can have --data
from the script above contain this payload (as a string on a single line):
{
"aps": {
"alert": {
"body": "Push notification body",
"title": "Push notification title"
},
"mutable-content": 1,
category: "rich-apns"},
"media-url": "https://i.imgur.com/t4WGJQx.jpg"
}
If you would like to know more about how to compose and send rich push notifications you can read my post here.
Troubleshooting
Unfortunately, the feedback returned from APNS when something goes wrong leaves a lot to be desired. These are the status codes that are possible to be returned and are shown in the output of the previous script.
200: Success400: Bad request403: There was an error with the certificate or with the provider authentication token405: The request used a bad :method value. Only POST requests are supported.410: The device token is no longer active for the topic.413: The notification payload was too large.429: The server received too many requests for the same device token.500: Internal server error503: The server is shutting down and unavailable.
If the status code is 200
all is well and you received the push notification.
In general, you should always use a recent device token, the device token should actually be from the device you are trying to send to and you should make sure that team IDs, key IDs, and bundle IDs are spelled correctly.
For a successful request, the body of the response is empty. On failure, the response body contains a JSON dictionary with a reason
key that gives you a general idea of what went wrong e.g. BadDeviceToken.
Parting thoughts
I hope you have found this solution helpful for your push notification testing. I also hope that you found the information contained herein valuable enough for your own app development.
Thank you ❤️