A script that runs for twenty minutes, a cron job that should never fail, a CI build you are waiting on. Each is easier to live with if it can tap you on the wrist when it finishes. The shortest way there is one HTTP request to a service that already has an app on your phone.
Why not send it to Apple directly
Every push notification to an iPhone goes through Apple's push service, and Apple only accepts them from the developer of the app that will show them. Sending one yourself means a paid Apple Developer account, an app of your own on the phone, a signing key, and the device token that app registered. That is a weekend of work for a message saying a backup finished.
A push service has done that part. You install its app once, and your scripts send it plain HTTP.
The request
Sign in to Acciti, install the app on your iPhone or Mac, and create a sender token. Put the token and your user key in the environment:
export ACCITI_TOKEN=...export ACCITI_USER=...Then any script can do this:
curl -fsS https://acciti.com/v1/messages \ -H "Authorization: Bearer $ACCITI_TOKEN" \ -H "Content-Type: application/json" \ -d '{"user": "'"$ACCITI_USER"'", "title": "Deploy", "message": "Finished in 4m 12s"}'14:07
Deploy
nowFinished in 4m 12s
Scripts · chime
message takes up to 1024 characters, and title, sound, url and priority are optional.
Tell me when this command finishes
A shell function wraps any long command and reports whether it worked:
notify() { "$@" status=$? if [ "$status" -eq 0 ]; then result="finished"; else result="failed ($status)"; fi curl -fsS https://acciti.com/v1/messages \ -H "Authorization: Bearer $ACCITI_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"user\": \"$ACCITI_USER\", \"title\": \"$1 $result\", \"message\": \"$*\"}" > /dev/null return "$status"} notify make releaseIt returns the command's own exit status, so it can sit inside other scripts without hiding failures.
From cron
For a job on a schedule, the most useful alert is the one that fires when the job didn't run at all. That is a heartbeat rather than a message:
0 3 * * * /usr/local/bin/backup.sh && curl -fsS https://acciti.com/v1/heartbeats/$HEARTBEAT_TOKEN/pingIf the ping doesn't arrive on time, your phone rings. Monitoring backups has recipes for restic, borgmatic, rsync and systemd.
From GitHub Actions, only on failure
Store the token and user key as repository secrets, and add a last step that runs only when an earlier one failed:
- name: Tell my phone if: failure() env: ACCITI_TOKEN: ${{ secrets.ACCITI_TOKEN }} ACCITI_USER: ${{ secrets.ACCITI_USER }} run: | curl -fsS https://acciti.com/v1/messages \ -H "Authorization: Bearer $ACCITI_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"user\": \"$ACCITI_USER\", \"priority\": 1 \"title\": \"${{ github.workflow }} failed\", \"message\": \"${{ github.repository }} on ${{ github.ref_name }}\", \"url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}"The url opens the failed run when you tap the notification. To hear about pushes, releases and failed checks across every repository without editing workflows, use the GitHub connector instead.
From Python
import os, urllib.request, json request = urllib.request.Request( "https://acciti.com/v1/messages", data=json.dumps({ "user": os.environ["ACCITI_USER"], "title": "Training run", "message": "Epoch 40 done, loss 0.031", }).encode(), headers={ "Authorization": f"Bearer {os.environ['ACCITI_TOKEN']}", "Content-Type": "application/json", },)urllib.request.urlopen(request)Standard library only, so it works in a notebook or a container with nothing installed.
From Node
await fetch("https://acciti.com/v1/messages", { method: "POST", headers: { Authorization: `Bearer ${process.env.ACCITI_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ user: process.env.ACCITI_USER, message: "Import finished: 48,210 rows", }),})Picking a priority
Most script notifications are Normal: a sound and a banner, held during your quiet hours. Use -1 for a banner with no sound, and 1 for something that should get through Focus. Keep 2, Emergency, for alerts that should repeat until you acknowledge them, such as production being down.
A script that sends Emergency for a finished build will teach you to ignore Emergency. The API documentation has every field, with examples in Swift and Go as well.