Library / SDK
hoodie/notify-rust avatar
hoodie/notify-rust

notify-rust: desktop notifications from a Rust program, per platform

☝️send desktop notifications from your Rust app.

1,429 stars102 forksRustApache-2.0

At a glance

What is it?
notify-rust shows desktop notifications on Linux, macOS and Windows from Rust, using the XDG notification spec on Unix desktops, mac-notification-sys on macOS and winrt-notification on Windows. It is dual licensed Apache-2.0 or MIT, the current release is 4.18.2 from 2026-10-07, and the feature set differs substantially per platform.
Who is it for?
notify-rust fits a Rust application that needs to tell the user something without stealing focus, particularly on a Linux desktop where the XDG implementation is the mature path and where the optional image and dbus-rs features matter. It does not fit a project that needs identical behaviour on all three platforms, because the macOS support covers only a subset of the functionality and the crate itself says so.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 7 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One builder API, three completely different backends

notify-rust is described, with some self-awareness, as a not so well-named library for displaying desktop notifications. Its job is to put a notification on the user's screen from a Rust program, and the interesting part is that the three supported platforms have almost nothing in common underneath.

On Linux and BSD, the library talks the XDG notification specification, which is what GNOME, KDE, XFCE, LXDE, MATE and most other desktop environments implement. The README names the GNOME developer documentation and the libnotify specification as the references, and says the main audience of the library are Linux and BSD based desktop environments that follow the XDG specification.

On macOS, support came later, thanks to `mac-notification-sys`, and the library is explicit about the limitation: it only includes a small subset of the current functionality, because `NSNotification`s do not have as many features. The README phrases this bluntly, with a call for participation aimed at people who are versed macOS UI developers with Objective-C skills.

On Windows, support comes via `winrt-notification`, described in the same terms as macOS, as similar.

So the shape of the crate is a single fluent API over three backends with different capabilities. That is worth understanding before you design around it, because a feature you rely on may only exist on one of the three platforms.

The builder is the whole API, and hints are how you reach beyond it

The basic usage is a builder chain that ends in `show()`:

rust
use notify_rust::Notification;
Notification::new()
    .summary("Firefox News")
    .body("This will almost look like a real firefox notification.")
    .icon("firefox")
    .show()?;

The `summary` is the title, `body` is the text, `icon` names an icon by theme name rather than a file path, and `show()` delivers it and returns a result.

The more interesting call is `hint`, which is how you reach platform-specific behaviour. The persistent notification example uses two hints:

rust
use notify_rust::{Notification, Hint};
Notification::new()
    .summary("Category:email")
    .body("This has nothing to do with emails.\nIt should not go away until you acknowledge it.")
    .icon("thunderbird")
    .appname("thunderbird")
    .hint(Hint::Category("email".to_owned()))
    .hint(Hint::Resident(true)) // this is not supported by all implementations
    .timeout(0) // this however is
    .show()?;

The inline comments in that example are the documentation. `Hint::Resident(true)` keeps the notification on screen, and the comment says plainly that this is not supported by all implementations. `timeout(0)` asks for the notification not to disappear, and the comment says that this however is supported. So the two mechanisms for persistence are not equivalent, and the difference matters on a Linux desktop where the notification daemon decides what it does with a hint.

`appname` sets the application name shown alongside the notification, which is what makes a notification look like it comes from Thunderbird rather than from an anonymous process.

The images feature is heavier than it looks, but image_path is not

Sending images in a notification is one of those features whose cost depends entirely on how you do it, and the crate draws the line clearly.

The `images` feature enables the `image_data()` pixel-buffer API, and it is described as only supported on XDG. It requires the `image` crate and `lazy_static`, the latter to determine the supported spec version on startup, which is a per-session capability probe of the notification daemon. The `images` feature in the manifest expands to `images_no_default_features` plus `image/rayon` and `image/default-formats`, so enabling it pulls in the image decoding stack and its parallel decoding support.

By contrast, `image_path()` needs no feature on any platform, and the documentation is explicit about it. On Linux and BSD it maps to the `image-path` XDG hint, on macOS it maps to `content_image` in `mac-notification-sys`, and on Windows it is passed directly to `winrt-notification`, all without any additional dependencies.

That is a useful design decision for an application that already has an icon file on disk. You get a platform-appropriate image notification with no extra compile-time cost, and you only pay for the pixel-buffer path if you genuinely need to send raw image data.

The other feature flag in the manifest is `d`, which enables using `dbus-rs` instead of `zbus`, also XDG only and functionally identical to the default set. The warning about it is one of the more useful pieces of documentation in the crate: as long as you still compile with default features, enabling `d` will only enable dbus-rs usage but not default to it. To actually use the dbus-rs implementation you have to set the environment variable `DBUSRS` or compile with `--no-default-features`. The default feature set is `z`, which pulls in `zbus`, `serde` and `async`.

The example directory is a catalogue of what the API can do

The `examples/` directory is where the real API surface is visible, and it is a long one. The list includes `actions.rs`, `at_bus.rs`, `close.rs`, `countdown.rs`, `formatting.rs`, `hints.rs`, `image_path.rs`, `images.rs`, `info.rs`, `interruption_level.rs`, `mac_app_id.rs`, `minimal.rs`, `on_close.rs`, `persistent.rs`, `progress.rs`, `response.rs`, `reuse.rs`, `schedule.rs`, `show_volume.rs`, `simple.rs`, `simple_async.rs`, `sound.rs` and `stack.rs`.

Reading that list against the API tells you what the crate can do. `actions.rs` is notification buttons. `on_close.rs` and `close.rs` are the close callback and programmatic dismissal. `response.rs` is handling the user's answer to an action. `progress.rs` and `countdown.rs` are progress indicators, and `show_volume.rs` is a volume indicator. `schedule.rs` is delayed delivery, `stack.rs` is replacing an existing notification in the stack, `reuse.rs` is updating a notification you already showed, `at_bus.rs` is addressing a specific bus, `info.rs` is querying the server, `interruption_level.rs` is urgency, `formatting.rs` is markup in the body, `sound.rs` is a sound, `interruption_level.rs` maps to the XDG urgency hint, and `mac_app_id.rs` is the macOS application identifier.

There is also an `octodex.jpg` in the directory, which is the image used by the image examples.

Two directories of infrastructure sit alongside. `capabilities.md` is the document to read before you commit to a feature, since it describes what the notification server on a given desktop supports. And `server_infos.yaml` looks like a table of server capabilities, which is the kind of thing you need when a single API call behaves differently on GNOME, KDE and a tiling window manager.

The `common/` subdirectory holds shared example code, and `run_bundled_example.sh` is a helper for running the examples against a local daemon, which is how you test a notification without the noise of a real desktop.

The manifest is a map of every platform combination it supports

The `Cargo.toml` is unusually instructive, because the dependency table is split by target platform and each cell tells you something.

The package is `notify-rust` at version 4.18.2, edition 2021, with a minimum Rust version of 1.89.0, and the licence field reads `MIT OR Apache-2.0`. The description says it shows desktop notifications on linux, bsd and mac, and describes itself as a pure Rust dbus client and server.

The Unix-but-not-macOS target has `dbus` at 0.9, `zbus` at 5, `lazy_static`, `image` and `serde` as optional dependencies, plus `env_logger` marked with a TODO to remove it and `futures-lite` at 2.6.1 as a non-optional one. The macOS target has `mac-notification-sys` at 0.6.15, `mac-usernotifications` and `chrono` as optional, and a separate Windows target block with `winrt-notification`.

The features list is a catalogue of the choices. `z` is the default and pulls in `zbus`, `serde` and `async`. `z-with-tokio` pulls in `zbus`, `serde` and `tokio`, `async` enables `zbus/async-io`, and `tokio` enables `zbus/tokio`. `d` and `d_vendored` select the dbus-rs implementation. `debug_namespace` is a feature with no dependencies, which is what a namespace override for debugging D-Bus traffic would be. `preview-macos-un` pulls in `mac-usernotifications` and is commented out of the default set, which is how an experimental macOS path is kept out of the way.

The `include` list is also deliberate. It names exactly what goes into the published crate: the readme, the history and changelog files, both licence files, the manifest, `src/**/*.rs` and `tests/*.rs`. The `examples/` directory is not included, so the example code is not part of the download from crates.io, which keeps the published crate small even though the examples are extensive.

Where notify-rust is the wrong tool

The first case is a project that needs the same behaviour on all three platforms. The crate is honest that macOS supports only a subset of the functionality, and the Windows path is described as similar. If your product needs actions, or a resident notification, or an image, to work identically for a Linux user and a macOS user, you will be writing platform branches, and the crate's API will not hide that from you.

The second case is an application where a notification is not the right channel. A desktop notification is transient, it can be suppressed by the user's do-not-disturb setting, it disappears, and on Linux the notification daemon decides what happens to it. If the message is important enough that losing it is a failure, a notification is the wrong delivery mechanism, and no library changes that.

The third case is a project that wants no dependencies on the notification stack at all. On a Linux desktop, sending a notification means speaking D-Bus to a daemon that speaks XDG. That is a well-trodden path and this crate is the good way along it, but it does mean your process interacts with a user session service, which matters in a headless or containerised environment where there is no session bus to talk to.

There is one licensing nuance worth reading properly, since the crate is dual licensed. It is licensed under either the Apache License 2.0 or the MIT licence at your option, with the two licence files at the root of the repository. And the contribution terms say that unless you explicitly state otherwise, any contribution is dual licensed under those same terms, without any additional terms or conditions.

Editorial conclusion

notify-rust fits a Rust application that needs to tell the user something without stealing focus, particularly on a Linux desktop where the XDG implementation is the mature path and where the optional image and dbus-rs features matter. It does not fit a project that needs identical behaviour on all three platforms, because the macOS support covers only a subset of the functionality and the crate itself says so. Before depending on it, check the `capabilities.md` file in the repository against the notification features you plan to use, and be aware that the desktop notification daemon on Linux is what actually decides what a user sees.

Frequently asked questions

How do I add notify-rust to a Rust project?

Add the dependency to your manifest, `notify-rust = "4"`, and use the builder, starting with `Notification::new().summary(...).body(...).icon(...).show()`. The current release is 4.18.2, and the crate needs Rust 1.89.0 or newer.

How do I make a notification stay on screen?

There are two mechanisms with different support. `Hint::Resident(true)` keeps the notification resident but is not supported by all implementations, while `timeout(0)` asks for the notification not to disappear and is more widely supported. The crate's own example uses both.

Does sending an image require an extra crate feature?

Only the pixel-buffer API does. The `images` feature is needed for `image_data()` and pulls in the image crate, while `image_path()` needs no feature on any platform, mapping to the image-path XDG hint on Linux, content_image on macOS and winrt-notification on Windows.

Which platforms does notify-rust support well?

Linux and BSD are the original target, through the XDG notification specification, and they support the full feature set. macOS support was added later through mac-notification-sys and covers only a small subset of the functionality because NSNotifications have fewer features, and Windows is supported through winrt-notification.

Official sources

  1. hoodie/notify-rust on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/hoodie-notify-rust.svg)](https://hysenlabs.com/projects/hoodie-notify-rust)

Was this useful?