newline

How to have Emacs notify you when it needs your attention

Guide, Emacs

July 01, 2025

At times I have a long-running process within Emacs. For example, a package update and upgrade, or a calendar synchronization. In those moments, I also want to do some other work. However, Emacs might issue prompts for yes/no answers etc. at various points in the process, and I would either have to check back periodically for such prompts, or risk working on something for an hour while thinking that Emacs is doing stuff in the background, when actually it just waits for user input the whole time, making the whole process take longer. It would be ideal if Emacs notified me whenever it required input. I couldn’t find an option like that out of the box. Thankfully, Emacs is extendable, so I can add such functionality myself.

In this post, I’m assuming you know how to configure Emacs and the basics of elisp.

There are functions commonly used by developers in Emacs to prompt the user for input: y-or-n-p, yes-or-no-p, and others. The general idea is to, before each of these functions, call a custom function that sends a notification. One big caveat is this method relies on the advised functions (e.g. y-or-n-p or user-error) actually being called at some point in the target function (e.g. org-caldav-sync). So if a developer writes their own prompting/error code, you’ll need to do some more investigation. However, generally, people tend to use the built-in functions.

We need to create three things: a generic notifier function that can dispatch to whatever OS-specific notification method is available, a handler that will call that generic function, and some function calls to register our handler.

First, a generic notifier function:

(defun za/notify (title message)
  "Show notification with TITLE and MESSAGE."
  (cond ((fboundp 'ns-do-applescript)
         (ns-do-applescript
          (format "display notification \"%s\" with title \"%s\""
                  (replace-regexp-in-string "\"" "#" message)
                  (replace-regexp-in-string "\"" "#" title))))
        ((string= system-type "gnu/linux")
         (require 'notifications)
         (notifications-notify :title title :body message))
        (t (error "No notification handler defined!"))))

It’s just a wrapper around whatever command I can use on an OS to send a notification, with a title and a message. If we’re on macOS (detected by checking if the function ns-do-applescript exists), use AppleScript. If we’re on GNU/Linux, use D-Bus (via notifications-notify, loaded by the require call). I don’t use Windows, so I don’t have that set up, but you could pretty easily extend this for Windows and anything else you use.

Then a handler to notify specifically about an interaction request:

(defun za/send-notification-interactivity-required (&rest _)
  "Notify that a function needs action."
  (za/notify "Interactivity required" "A function requires interactivity."))

Not much to add here, it just calls the generic notifier function with a specific title and message.

Now here’s the most important part: tell Emacs to call the custom function before any function that requires interactivity:

(defun za/notify-on-interactivity (func &rest r)
  "Send a notification whenever FUNC requires interactivity.
Used as :around advice, calling FUNC with arguments R."
  (advice-add #'y-or-n-p :before #'za/send-notification-interactivity-required)
  (advice-add #'yes-or-no-p :before #'za/send-notification-interactivity-required)
  (advice-add #'user-error :before #'za/send-notification-interactivity-required)
  (with-demoted-errors "Error in %s" (apply func r))
  (advice-remove #'y-or-n-p #'za/send-notification-interactivity-required)
  (advice-remove #'yes-or-no-p #'za/send-notification-interactivity-required)
  (advice-remove #'user-error #'za/send-notification-interactivity-required))

We’re using the advice-add mechanism here, which allows you to modify already defined functions in some way: call a custom function before/after a specified function, filter a function’s arguments prior to calling it, completely replace a function, etc. In our case, we use the form (advice-add target-func :before custom-func): call custom-func before you call target-func. In this way, we can call our notifier function before a yes-or-no prompt, or on an error message (often displayed via user-error). Note – yes, this code can be improved with e.g. a loop/map call, this is just the first version.

Also, importantly, we wrap the call to whatever function we’re modifying (“func”) in a call to with-demoted-errors. Imagine what would happen if func throws an error: we would immediately return from our wrapper function, leaving dangling advice on functions like y-or-n-p. We don’t always want to advise them, we only want that within the call stack of specific functions, so we have to make sure the advice-remove calls happen no matter what func does. Surrounding it in a call to with-demoted-errors is one way to do that.

This function za/notify-on-interactivity itself is intended to be used as an :around advice. To finish this up, let’s say I want to be notified whenever org-caldav-sync requires user input. I can put the following in my config:

  (advice-add #'org-caldav-sync :around #'za/notify-on-interactivity)