Push notifications from shell scripts and cron
PushPiper turns any shell script or cron job into a push notification on your phone, free for 10,000 messages a month. All it takes is curl, which is already on almost every server and laptop.
Before you start
- Sign in at pushpiper.com/app (free, no credit card) and create an app. Its API token is your
APP_TOKEN. - Copy your user key from the dashboard. That's your
USER_KEY. - Export both in your shell profile so every script can pick them up.
export PUSHPIPER_TOKEN=APP_TOKEN
export PUSHPIPER_USER=USER_KEYSend a notification
curl -sS https://api.pushpiper.com/1/messages.json \
--form-string "token=$PUSHPIPER_TOKEN" \
--form-string "user=$PUSHPIPER_USER" \
--form-string "message=Backup finished"#!/bin/sh
# Usage: push "message" ["title"]
exec curl -fsS -o /dev/null https://api.pushpiper.com/1/messages.json \
--form-string "token=${PUSHPIPER_TOKEN:?PUSHPIPER_TOKEN is not set}" \
--form-string "user=${PUSHPIPER_USER:?PUSHPIPER_USER is not set}" \
--form-string "title=${2:-$(uname -n)}" \
--form-string "message=${1:?usage: push MESSAGE [TITLE]}"push() {
curl -fsS -o /dev/null https://api.pushpiper.com/1/messages.json \
--form-string "token=${PUSHPIPER_TOKEN:?PUSHPIPER_TOKEN is not set}" \
--form-string "user=${PUSHPIPER_USER:?PUSHPIPER_USER is not set}" \
--form-string "title=${2:-$(uname -n)}" \
--form-string "message=${1:?usage: push MESSAGE [TITLE]}"
}Success returns {"status":1,"request":"..."}. --form-string sends values exactly as written, so a message that starts with @ or < isn't mistaken for a file.
Save the script as /usr/local/bin/push, run chmod +x on it, and any script on the box can call push "Disk almost full". The title defaults to the hostname. -fsS -o /dev/null keeps it silent on success, prints an error when the API says no and exits with curl's status. Prefer a function? Drop the second one into your ~/.bashrc.
Cron jobs
PUSHPIPER_TOKEN=APP_TOKEN
PUSHPIPER_USER=USER_KEY
# Only when the backup fails, with its exit code
30 2 * * * /usr/local/bin/backup.sh || /usr/local/bin/push "Backup failed (exit $?)" "Backup"
# Only when the sync succeeds
0 6 * * * /usr/local/bin/sync.sh && /usr/local/bin/push "Nightly sync done"
# Escape % in crontab lines
0 7 * * 1 /usr/local/bin/push "Weekly report for $(date +\%F)"Cron runs jobs with /bin/sh and a bare environment, so use full paths and set the two variables at the top of the crontab. After ||, $? still holds the failed job's exit code, so the alert tells you how it failed.
A % in a crontab line turns into a newline, so escape it as \%, like in the date example.
Long commands, photos and emergencies
make release; push "make release finished with exit code $?"curl -fsS https://api.pushpiper.com/1/messages.json \
--form-string "token=$PUSHPIPER_TOKEN" \
--form-string "user=$PUSHPIPER_USER" \
--form-string "message=Someone is at the door" \
-F "attachment=@snapshot.jpg;type=image/jpeg"curl -fsS https://api.pushpiper.com/1/messages.json \
--form-string "token=$PUSHPIPER_TOKEN" \
--form-string "user=$PUSHPIPER_USER" \
--form-string "title=Server room" \
--form-string "message=Temperature is 41 C" \
--form-string "priority=2" \
--form-string "retry=60" \
--form-string "expire=3600"Invoke-RestMethod -Method Post -Uri https://api.pushpiper.com/1/messages.json -Body @{
token = $env:PUSHPIPER_TOKEN
user = $env:PUSHPIPER_USER
title = "Backup"
message = "Backup finished on $env:COMPUTERNAME"
}Photos go up as multipart, up to 5 MB. Emergency alerts repeat every retry seconds (30 or more) until someone acknowledges them, for up to expire seconds (10800 at most), and the response includes a receipt. On Windows, Invoke-RestMethod sends the hashtable as a regular form post.
Useful fields
| Field | Example | What it does |
|---|---|---|
priority | 1 | From -2 (no alert) to 2 (emergency). 1 bypasses quiet hours. |
sound | siren | One of the 23 built-in sounds, none included. |
url, url_title | https://status.example.com | A link under the message. |
monospace | 1 | Fixed-width text for command output. Use html=1 for HTML instead, not both. |
ttl | 3600 | Deletes the notification after that many seconds. |
device | phone | Sends to one of your devices only. |
Everything else is in the API reference.
Limits and caveats
- Sizes. Messages up to 1,024 characters, titles 250, attachments 5 MB.
- Errors. HTTP 400 with an
errorslist for bad input, 429 once you're over your monthly quota. Every send response carriesX-Limit-App-Limit,X-Limit-App-RemainingandX-Limit-App-Resetheaders; add-ito the one-liner to see them. - Keys. Keep them in the environment or a file only you can read, not in scripts you commit.
- Cron mail. The push script prints nothing on success, so it only adds to cron mail when sending fails.
Sources
Questions and answers
Do I need to install anything?
No, just curl, which ships with most Linux distributions, macOS and Windows 10 and 11. On Windows you can also use PowerShell's Invoke-RestMethod.
How do I get a push only when a cron job fails?
Chain the alert with ||, like backup.sh || push "Backup failed (exit $?)". The push only runs when the job exits with an error, and $? holds its exit code.
Can I send to several people?
Yes. Put up to 50 user keys, separated by commas, in user, or use a group key.
What happens after 10,000 messages?
The API answers HTTP 429 until your quota resets on the 1st of the month. Pro raises the limit to 50,000 messages for EUR 4 a month.