Edit: While this tutorial focuses on macOS with terminal-notifier, Linux users can achieve a similar result by replacing it with notify-send.

How many times have you run a long process on your Mac, constantly peeking at the terminal to see if it’s done?

Sure, you can do:

echo $'\a'  # beep!

…but if you walk away or have your volume muted, that won’t help you. Wouldn’t it be nicer to get a real macOS notification that appears in your Notification Center? And guess what: that works even when you’re running commands over SSH from a remote machine!

Image description

This post shows you how, using the terminal-notifier tool and a neat SSH trick.


1. Meet terminal-notifier

terminal-notifier is a command-line utility that lets you create macOS notification banners from any script or terminal command. You can include messages, custom icons, sounds, and even open a URL when clicked.

Installation

brew install terminal-notifier

(If you don’t have Homebrew, install from brew.sh)

Usage

Once installed, try:

terminal-notifier -message "Build finished!" -sound Blow

You’ll see a native macOS notification in the corner of your screen. No more missed beep!


2. Using terminal-notifier Locally

If you’re on your Mac and running a long command:

my-long-command; terminal-notifier -message "Command Done"

Or if you want to notify on both success and failure:

my-long-command
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
  terminal-notifier -message "✅ All good!"
else
  terminal-notifier -message "❌ Failed with code $EXIT_CODE"
fi

That’s easy enough. But what if your command runs on a remote machine accessed via SSH?


3. Getting Notifications from Remote Commands

When you SSH into a remote server, that server can’t directly call terminal-notifier on your Mac. The solution: send a message back to your Mac (via SSH port forwarding) and let your local Mac run terminal-notifier.

One simple approach:

  1. On your Mac: open a reverse SSH tunnel and listen with netcat (nc).
  2. On the remote: send the message "Beep" or "OK" to the tunnel, which arrives at your Mac.
  3. Your Mac: reads the message and triggers terminal-notifier.

Example Script on Your Mac

#!/bin/bash
REMOTE_USER="user"        # your username on remote
REMOTE_HOST="remote"      # remote host, e.g. myserver.com
LOCAL_PORT=5050
REMOTE_PORT=4000

# 1) Create a reverse SSH tunnel, so remote:4000 => mac:5050
ssh -f -N -R ${REMOTE_PORT}:127.0.0.1:${LOCAL_PORT} ${REMOTE_USER}@${REMOTE_HOST}

# 2) Listen on port 5050 locally
while true; do
  nc -l ${LOCAL_PORT} | {
    read line
    # Trigger terminal-notifier when we get input
    terminal-notifier -message "Received from remote: $line" -sound Blow
  }
done
  • ssh -f -N -R: opens a reverse tunnel but doesn’t start an interactive shell.
  • nc -l ${LOCAL_PORT}: listens for connections on your Mac.

Remote Side

On the remote server, once you’re inside an SSH session (or in tmux):

printf "OK\n" | nc 127.0.0.1 4000

Now the text OK is forwarded through the SSH tunnel to your Mac’s port 5050, your Mac sees it, and terminal-notifier pops up!


4. Quick Aliases

If you want to make it even simpler:

Remote server (e.g., in your ~/.bashrc or ~/.zshrc):

# Create a quick notify alias for sending a beep message
alias notify='echo "Beep" | nc 127.0.0.1 4000 > /dev/null 2>&1 &'

Local Mac:

# Use terminal-notifier with a default message + sound
alias notify='terminal-notifier -message "Beep!" -sound Blow'

So any time you want to trigger the Mac notification:

time-consuming-process; notify

For example:

python train.py; notify

5. Wrap-Up

Long-running tasks are no longer a “check every 5 minutes” chore. Let your Mac’s built-in notification system do the heavy lifting!

  • Install terminal-notifier
  • Set up an SSH reverse tunnel
  • Use notify alias after long commands
  • Done: You’ll get a nice macOS banner when your remote job finishes.

Happy building!

Credits


Additional Notes

Advanced: Auto-run Your Local Listener Script via LocalCommand

Let’s say you want the listener to automatically run every time you connect to a particular host, without manually starting the script. You can leverage an SSH config trick called LocalCommand:

  1. Edit ~/.ssh/config on your Mac (or create if it doesn’t exist) and add something like:
Host myremote
  HostName remote.example.org
  User yourusername
  RemoteForward 4000 127.0.0.1:5050
  PermitLocalCommand yes
  LocalCommand /Users/youruser/bin/notify_tunnel_listener.sh &
  1. notify_tunnel_listener.sh (on your Mac) can be:
#!/bin/bash
# If already running, do nothing:
pgrep -f "nc -l 5050" && exit 0

while true; do
  nc -l 5050 | {
    read line
    terminal-notifier -message "Remote says: $line"
  }
done

Make it executable with:

chmod +x /Users/youruser/bin/notify_tunnel_listener.sh
  1. Now, whenever you ssh myremote, it sets up the reverse tunnel (RemoteForward), and runs your local listener script automatically (LocalCommand). The pgrep line prevents multiple listeners from stacking up.

A Note on Foreground vs Background

By default, LocalCommand might block your SSH session if you run a while true script. You can put an ampersand (&) to run it in the background:

LocalCommand /Users/youruser/bin/notify_tunnel_listener.sh &

You’ll need to handle stopping it yourself or let it run in the background indefinitely. One strategy is using the pgrep check above.


Docker or Apptainer

If you're running Docker or Apptainer inside a remote machine, keep in mind that the container's localhost may not be the same as the host's localhost. Thus, printf "OK\n" | nc 127.0.0.1 4000 might fail. The simplest fix is to run your container with host networking:

  • Docker:
docker run --network=host ...
  • Apptainer:
apptainer shell --net=host image.sif

Also ensure that nc (netcat) is installed inside the container, because that's what actually sends the message back to the remote host. Once the container shares the host network and has nc available, everything works just as described above.