← All use cases
Operations

Cron job progress on your Lock Screen

Show scheduled jobs as real-time progress while they run, then send a final Push Notification when the job finishes or fails.

Scheduled jobs often run in the background for minutes or hours: backups, imports, customer syncs, report generation, cleanup tasks, and nightly maintenance.

When the only status lives in logs, it is easy to miss whether the job is still running, stuck in one stage, already finished, or failed before handoff.

ActivitySmith keeps the current job visible as a Live Activity and can send a Push Notification when the final result is known.

Before you start

ActivitySmith account

Create an account so you can generate an API key for this workflow.

API key in your runtime

Store your ActivitySmith API key as ACTIVITYSMITH_API_KEY in the service, worker, or environment that sends the event.

At least one paired iOS device

Pair an iPhone or iPad with ActivitySmith so the update has somewhere to appear.

Add progress to a scheduled job

Live Activity progress

Use a Live Activity when the job has phases worth watching while it runs, such as extract, transform, verify, publish, or cleanup.

Step 1

Start progress when the job begins

Create a Live Activity for the scheduled run so the progress belongs to that job timeline.

ActivitySmith Live Activity showing the start of a cron job

Step 2

Update after each real milestone

Update the subtitle and current step only when the job reaches meaningful states.

ActivitySmith Live Activity showing cron job progress update

Step 3

End progress when the job is done

End the Live Activity on success or failure so old job status does not linger.

ActivitySmith Live Activity showing completed cron job progress

Completion Push Notification

Send a Push Notification when the job finishes, fails, or needs a retry so the final result is not buried in scheduler logs.

ActivitySmith Push Notification showing cron job completion on iPhone

Team visibility

Invite teammates so operations, DevOps, or the people responsible for the job can pair their own devices and see scheduled run status.

Use channels to target specific people or devices, such as operations, devops, or data.

Reference implementation

Schedule the job with cron

Cron can run the same script that already performs the job. Load the environment first so the script can read the ActivitySmith API key, database URL, and storage credentials.

# /etc/cron.d/nightly-backup
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
0 2 * * * deploy . /etc/nightly-backup.env; cd /srv/app && ./scripts/nightly-backup.sh >> /var/log/nightly-backup.log 2>&1

Track a nightly backup from Bash

Wrap the real backup commands with ActivitySmith calls. Start progress before the expensive work, update after each milestone, end the Live Activity, and send a Push Notification for the final result.

#!/usr/bin/env bash
set -euo pipefail
ACTIVITY_ID=""
CURRENT_STEP=1
BACKUP_FILE="/tmp/nightly-backup-$(date +%F).dump"
api_post() {
local path="$1"
local payload="$2"
curl -fsS -X POST "https://activitysmith.com/api/$path" \
-H "Authorization: Bearer $ACTIVITYSMITH_API_KEY" \
-H "Content-Type: application/json" \
-d "$payload"
}
start_activity() {
ACTIVITY_ID=$(api_post "live-activity/start" "$(jq -n '{
channels: ["operations"],
content_state: {
title: "Nightly database backup",
subtitle: "create snapshot",
type: "segmented_progress",
number_of_steps: 4,
current_step: 1,
color: "green"
}
}')" | jq -r '.activity_id')
if [[ -z "$ACTIVITY_ID" || "$ACTIVITY_ID" == "null" ]]; then
echo "ActivitySmith did not return an activity_id" >&2
exit 1
fi
}
update_activity() {
local subtitle="$1"
local step="$2"
[[ -z "$ACTIVITY_ID" ]] && return
CURRENT_STEP="$step"
api_post "live-activity/update" "$(jq -n \
--arg activity_id "$ACTIVITY_ID" \
--arg subtitle "$subtitle" \
--argjson step "$step" \
'{
activity_id: $activity_id,
content_state: {
title: "Nightly database backup",
subtitle: $subtitle,
current_step: $step
}
}')" >/dev/null
}
end_activity() {
local subtitle="$1"
local step="$2"
[[ -z "$ACTIVITY_ID" ]] && return
api_post "live-activity/end" "$(jq -n \
--arg activity_id "$ACTIVITY_ID" \
--arg subtitle "$subtitle" \
--argjson step "$step" \
'{
activity_id: $activity_id,
content_state: {
title: "Nightly database backup",
subtitle: $subtitle,
current_step: $step,
auto_dismiss_minutes: 2
}
}')" >/dev/null
}
send_push() {
local title="$1"
local message="$2"
api_post "push-notification" "$(jq -n \
--arg title "$title" \
--arg message "$message" \
'{
title: $title,
message: $message,
channels: ["operations"]
}')" >/dev/null
}
on_error() {
local exit_code=$?
end_activity "failed" "$CURRENT_STEP" || true
send_push "Nightly backup failed" "Check /var/log/nightly-backup.log for details." || true
exit "$exit_code"
}
trap on_error ERR
start_activity
pg_dump --format=custom --file "$BACKUP_FILE" "$DATABASE_URL"
update_activity "upload archive" 2
aws s3 cp "$BACKUP_FILE" "s3://acme-backups/postgres/$(basename "$BACKUP_FILE")"
update_activity "verify restore" 3
pg_restore --list "$BACKUP_FILE" >/dev/null
end_activity "finished" 4
send_push "Nightly backup complete" "Snapshot uploaded and restore check passed."
trap - ERR

Scheduled jobs without log watching

  • Long-running jobs stay visible while they run, not only after they fail.
  • Completion and failure become clear without opening scheduler dashboards.
  • Morning handoff starts with a known job state instead of a log hunt.

Track the next scheduled run

Start with one cron job that already takes a few minutes. Show its current stage on the Lock Screen and send the final result when it finishes.