Send Desktop Notifications from the Shell with notify-send
A build script that finishes silently in a background tab is a build script nobody notices; notify-send fixes that by popping a real desktop alert. By the end of this guide you can send one, pick an appropriate urgency, and check whether the local notification service actually accepted it. The examples target the installed Ubuntu package libnotify-bin, version 0.8.3-1build2, and the notify-send command documented by its local manpage.
The route
Jump straight to the step you need, or tick off Done means at the end.
Before you start
You need a graphical desktop session with a notification daemon running, plus the notify-send executable. No root access is required. Allow about five minutes for the basic examples, longer if you are wiring notifications into a script and need to test its session environment.
Check the package and command first:
command -v notify-send
dpkg-query -W libnotify-bin
Expect a path such as /usr/bin/notify-send and the installed package version:
libnotify-bin 0.8.3-1build2
Checkpoint
If command -v prints nothing, install the distribution package through your normal software-management process. Do not copy a binary from an untrusted download.
1. Send a notification with a summary and body
The summary is required; the body is optional and should carry the useful detail. Put them in separate arguments so spaces stay part of the text:
notify-send 'Backup finished' 'The documents backup completed successfully.'
The command normally prints nothing. A notification should appear through the desktop notification daemon, which controls the visual layout, so the same command can look completely different in GNOME, Plasma, or another desktop environment.
To test only the command-line interface without actually sending a notification, ask for help instead:
notify-send --help | sed -n '1,25p'
If the send command reports it cannot connect to a notification service, you are probably outside the graphical session, on a remote shell, or running from a service without the session's D-Bus environment. Re-running it with sudo usually makes this worse, since that changes the user and session context. Run it as the logged-in desktop user instead.
2. Choose urgency and an icon
Use --urgency to classify the notification as low, normal, or critical:
notify-send --urgency=low 'Sync complete' 'No files needed attention.'
notify-send --urgency=critical 'Disk check failed' 'Review the service log before continuing.'
Reserve critical for events that genuinely need attention: it can change how a desktop presents the alert, and Plasma may ignore an expiry time for critical notifications entirely. Add an icon by giving --icon a stock icon name or a filename that exists on the machine:
notify-send --icon=dialog-information 'Deployment' 'The staging deployment is ready.'
Icon names are interpreted by the notification service and the installed icon theme. If the alert appears without an icon, the message is probably still fine; test the chosen name on the target desktop before depending on it.
3. Set a lifetime, but do not assume it is enforced
--expire-time takes milliseconds:
notify-send --expire-time=5000 'Reminder' 'This notification requests five seconds on screen.'
That is a request to the notification daemon, not a guarantee. The local manpage specifically warns that GNOME Shell and Notify OSD ignore the value, and Plasma ignores it for critical notifications. The freedesktop.org notification specification also lets a server apply its own policy regardless.
Use --wait when a script must stay running until the notification closes:
notify-send --wait --expire-time=5000 'Job finished' 'The report is ready.'
With an expiry time set, that time becomes the maximum wait on implementations that honour it. Without one, the command may wait for the daemon's own chosen lifetime, or until the user closes it. Do not put --wait in a long-running service unless that pause is genuinely deliberate.
4. Replace an earlier notification
Ask the daemon for an ID with --print-id, then feed that ID to --replace-id. This suits progress-style status, where one alert should update in place instead of stacking up new ones:
notification_id=$(notify-send --print-id 'Build' 'Starting...')
notify-send --replace-id="$notification_id" 'Build' 'Finished.'
The first command writes the notification ID to standard output. That ID only means anything while the notification service's session is running. If the first command fails, the variable can end up empty, so a script should check the exit status before attempting replacement:
if notification_id=$(notify-send --print-id 'Build' 'Starting...'); then
notify-send --replace-id="$notification_id" 'Build' 'Finished.'
else
printf '%s' 'Notification service unavailable' >&2
fi
Replacement is not a durable record of progress. Keep logging important state separately, since a notification daemon can close alerts or restart at any point.
5. Add an action only when a user response is useful
An action adds a labelled choice and implies --wait. Give it a stable name before the equals sign so it can be read back from standard output:
action=$(notify-send --action=open='Open report' 'Report ready' 'Choose Open report to continue.')
printf 'Selected action: %s' "$action"
The name printed when the user activates the action here is open. Notification servers may ignore actions entirely, so scripts should handle an empty result, an interrupted wait, or a service failure. Never use an action as the only way to expose a security-sensitive decision or an irreversible operation.
Common traps and safe boundaries
- Notification text is not a terminal. Quote shell variables, especially ones with spaces or punctuation:
notify-send 'Status' "$message". - Do not treat --expire-time as a scheduler. It controls a requested display duration, not when a command runs.
- --transient asks for a non-persistent alert. Use it for ephemeral status, not for anything that must be recoverable later.
- Bodies support a small markup subset, but do not insert untrusted input as markup: plain text is the least surprising choice.
- Nothing here needs undoing. These examples change no files and require no elevated privileges; close a displayed notification through the desktop or let its daemon remove it.
Done means
- Installed and identified:
notify-sendresolves to the expected package. - Notification delivered: a quoted summary and body produce an alert in the intended desktop session.
- Urgency, icon and expiry chosen to match the message, with daemon limitations understood.
- Scripts check failures before using a printed notification ID or waiting on an action.