Monitor Cron Jobs with Android Alerts

Report the exit status of a scheduled job such as a nightly report or backup script.

QuickStart

Needs: Linux user cron + Bash + curl

  1. Install QueueRing on Android, register the device, allow notifications, and copy the current Queue ID. Keep the phone online.
  2. Store the ID, create two scripts, prepare a job, then schedule
  3. Verify on your device

Queue ID grants permission to send: keep it secret. If leaked, reissue it in the app and update your configuration. Security details.

202 only means accepted; display, history, and ID validity are not guaranteed. Resends can duplicate alerts; retry the notification only, never the job or agent. HTTP errors and retry advice.

1. Store your Queue ID once

Paste into Bash — save the private Queue ID
install -d -m 700 "$HOME/.config/queuering"
(
  set +x
  umask 077
  read -r -s -p 'Paste the Queue ID from your Android app: ' QUEUE_ID
  printf '\n'
  [[ "$QUEUE_ID" =~ ^qr1\.[A-Za-z0-9_-]{1,16}\.[A-Za-z0-9_-]{59}$ ]] || exit 1
  printf '%s\n' "$QUEUE_ID" > "$HOME/.config/queuering/queue-id"
  chmod 600 "$HOME/.config/queuering/queue-id"
)

Copy buttons copy code only. If unavailable, select the code and copy manually. Code messages are shared with the English examples.

Paste this into Bash on the sending machine. Enter the ID at the hidden prompt; it is stored outside your repository with restricted permissions.

2. Create the sender and wrapper

The setup creates notify-android.sh and nightly-with-alert.sh in $HOME/.local/bin. Before running, save a harmless test job containing exit 0 as $HOME/.local/bin/nightly-job.sh. If that path already contains a real job, review it first; do not overwrite or rerun a backup just to test an alert. Use the same cron user. Schedule only after the manual test succeeds.

Paste into Bash. It creates the two named files only when both names are unused. If either exists at the initial check, setup stops before writing either file. Review the existing scripts: use them only if they match this recipe, or choose unused names and update every corresponding path in setup (including the embedded wrapper) and run commands. Do not delete custom scripts just to retry. The sender handles HTTP, while the wrapper preserves the main command's exit code. Missing or unreadable ID configuration skips only the notification.

Paste into Bash — create two script files
(
  set -eu
  umask 077
  for file in "$HOME/.local/bin/notify-android.sh" "$HOME/.local/bin/nightly-with-alert.sh"; do
    if [[ -e "$file" || -L "$file" ]]; then
      printf 'Setup stopped: %s already exists. Existing files were kept. Review them before continuing.\n' "$file" >&2
      exit 1
    fi
  done
  install -d -m 700 "$HOME/.local/bin"
  set -C
  cat > "$HOME/.local/bin/notify-android.sh" <<'QUEUERING_SENDER'
#!/usr/bin/env bash
set +x
set -eu
set +x
: "${QUEUE_ID:?Load your Queue ID before sending}"
[[ "$QUEUE_ID" =~ ^qr1\.[A-Za-z0-9_-]{1,16}\.[A-Za-z0-9_-]{59}$ ]] || {
  printf 'Invalid Queue ID format; copy it from the app.\n' >&2
  exit 1
}
: "${1:?Pass a short notification message}"
message=$1
status=$(curl --silent --connect-timeout 10 --max-time 30 \
  --output /dev/null --dump-header /dev/stderr --write-out '%{http_code}' \
  --request POST "https://api.queuering.org/q/$QUEUE_ID" \
  --header 'Content-Type: text/plain; charset=utf-8' \
  --data-binary "$message") || {
  printf 'Notification transport failed; check connectivity.\n' >&2
  exit 1
}
printf 'Notification HTTP %s\n' "$status" >&2
[[ "$status" == 202 ]]
QUEUERING_SENDER
  cat > "$HOME/.local/bin/nightly-with-alert.sh" <<'QUEUERING_WRAPPER'
#!/usr/bin/env bash
set +x
set -eu
PATH=/usr/local/bin:/usr/bin:/bin
export PATH
result=0
bash "$HOME/.local/bin/nightly-job.sh" || result=$?
if ! IFS= read -r QUEUE_ID < "$HOME/.config/queuering/queue-id"; then
  printf 'Notification skipped: cannot read Queue ID configuration.\n' >&2
elif ! QUEUE_ID="$QUEUE_ID" bash "$HOME/.local/bin/notify-android.sh" "Nightly job finished (exit $result)"; then
  printf 'Notification failed; the cron job exit code is preserved.\n' >&2
fi
exit "$result"
QUEUERING_WRAPPER
)

3. Run the test

Continue only after setup succeeds, or after you have checked that the existing scripts match this recipe.

Paste into Bash — run the wrapper
bash "$HOME/.local/bin/nightly-with-alert.sh"
echo $?

Verify on your device

Use a harmless nightly-job.sh containing exit 0, then exit 7, and run bash "$HOME/.local/bin/nightly-with-alert.sh". Check both phone messages and echo $? (0 or 7). Temporarily schedule a near-future run to confirm the cron user's HOME, PATH, permissions, clock/timezone, and log destination before using the nightly schedule.

If HTTP 202 arrives without a phone alert, verify the current Queue ID, Android notification permission, and device connectivity first. Check the app's recent history as an additional observation, not a delivery receipt.

Next: register the cron job after the manual test succeeds

Cron registration and integration limits

Integration steps and limits

A Linux host with Bash, curl, and a running user cron service. The cron user must own the private Queue ID file and scripts.

Schedule the wrapper

Use crontab -e for the same user. The example runs at 02:00 in the cron daemon's configured timezone. The log contains job output and HTTP headers; keep it private and avoid writing credentials from your job.

Paste into Bash — prepare a private log
install -d -m 700 "$HOME/.local/state/queuering"
(umask 077; touch "$HOME/.local/state/queuering/cron.log")
chmod 600 "$HOME/.local/state/queuering/cron.log"

Save in crontab -e — run at 02:00
0 2 * * * /bin/bash "$HOME/.local/bin/nightly-with-alert.sh" >> "$HOME/.local/state/queuering/cron.log" 2>&1

This reports a job that actually ran and returned. It cannot detect an unstarted job, disabled cron, a hung command, host shutdown, or loss of network. Missed-run and host-down detection require an independent external monitor or heartbeat deadline. Avoid overlapping runs; this example adds no lock, deadline, or missed-run service.

Queue ID security details

Keep the Queue ID private

A Queue ID grants permission to send notifications to your device: treat it as a bearer credential. Never put a real ID in public repositories, Issues, logs, screenshots, prompts, or fixed sample values. Do not use shell tracing (set -x), curl verbose/trace output, or shared terminal recordings. The URL and process arguments can contain the ID; run only on a trusted machine and restrict proxy/access logs.

If exposed, reissue/rotate the Queue ID in the app and replace every local configuration or Actions secret using the old ID. Copy the new value from the app rather than editing the old one. A 202 response cannot confirm that an old ID remains valid.

API contract, HTTP errors, and retries

Understand the response and retry safely

Notifications and message history are best-effort. HTTP 202 means request acceptance, not guaranteed display on Android or guaranteed history storage. Invalid, inactive, and outdated Queue IDs intentionally receive the same 202 response. History stores up to 100 messages captured on the device; absence from history alone does not establish an HTTP failure.

The endpoint is POST https://api.queuering.org/q/{queueId}. Use UTF-8 text/plain; charset=utf-8, or application/json with a required nonempty body string and optional nonblank title. The raw request body, including JSON syntax, must be at most 4096 bytes.

HTTP responses and next steps
ResultWhat to do
202Check Android notification permissions, connectivity, and the current ID in the app. Do not retry merely because no notification appears.
400Fix the request, Content-Type, JSON syntax, and required fields before resending.
413Shorten the UTF-8 body to 4096 bytes or less; JSON escaping also counts.
429Wait at least Retry-After seconds when provided. If absent, use a delayed bounded retry with backoff rather than a tight loop.
503Temporarily unavailable. Inspect the failure and retry later with bounded backoff.
Transport failure / timeoutThe outcome may be unknown. Check connectivity before deciding to resend.

The samples send once, with a 10-second connect timeout and 30-second total timeout. They print HTTP status and response headers (including Retry-After), and do not automatically retry. Resending can create duplicate alerts; there is no idempotency key in these examples. Retry the notification only, never rerun a backup, deployment, or agent just to send its status. Keep your normal job logs and monitoring as the source of truth.

Try another recipe

All recipes · QueueRing home