alerter_rs 26.5.1

A library for sending macos desktop notifications in Rust (actually a thin wrapper around alerter)
docs.rs failed to build alerter_rs-26.5.1
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

alerter_rs

A Rust wrapper around vjeantet/alerter — send rich macOS user notifications from Rust without the user needing to install other programs in advance (and without having to create a bundled application).

The alerter binary is compiled from source at build time and embedded directly into your Rust binary. Users of your application never need to install alerter separately.

Requirements

  • macOS 13+
  • Swift toolchain (included with Xcode Command Line Tools)
  • Rust 1.56+ (2021 edition)

Installation

Add to your Cargo.toml:

[dependencies]
alerter_rs = "26.5"

Usage

Send a notification

use alerter_rs::Alerter;

let response = Alerter::new("Hello from Rust!")
    .title("My App")
    .subtitle("Important update")
    .sound("default")
    .send()?;

println!("User clicked: {}", response.activation_type);

Actions and dropdowns

let response = Alerter::new("Choose an option")
    .actions(vec!["Yes", "No", "Maybe"])
    .dropdown_label("Options")
    .send()?;

Reply notifications

let response = Alerter::new("What do you think?")
    .reply("Type your reply...")
    .send()?;

if let Some(reply_text) = response.activation_value {
    println!("User replied: {reply_text}");
}

JSON output

let response = Alerter::new("Click me")
    .json(true)
    .send()?;

Scheduled delivery

// Deliver after 30 seconds
Alerter::new("Reminder").delay(30).send()?;

// Deliver at a specific time
Alerter::new("Meeting soon").at("14:30").send()?;

Custom appearance

Alerter::new("Update available")
    .title("My App")
    .sender("com.apple.Safari")       // appear as Safari
    .app_icon("/path/to/icon.png")
    .content_image("/path/to/img.png")
    .close_label("Dismiss")
    .timeout(10)                       // auto-close after 10s
    .ignore_dnd(true)                  // bypass Do Not Disturb
    .group("updates")                  // replace previous in group
    .send()?;

Non-blocking (async) notifications

send_async() spawns the notification subprocess and returns immediately with a NotificationHandle, letting your program continue without waiting for user interaction.

Fire and forget

use alerter_rs::Alerter;

// Notification shows, handle is dropped, subprocess is killed
Alerter::new("Build complete!")
    .title("CI")
    .send_async()?
    .detach(); // detach so the notification persists after drop

Deferred wait

let handle = Alerter::new("Deploy to production?")
    .actions(vec!["Yes", "No"])
    .send_async()?;

// ... do other work while notification is showing ...

let response = handle.wait()?;
println!("User chose: {}", response.activation_type);

Managing multiple concurrent notifications

let mut handles = vec![
    Alerter::new("Task 1 complete").send_async()?,
    Alerter::new("Task 2 complete").send_async()?,
    Alerter::new("Task 3 complete").send_async()?,
];

// Poll and reap completed notifications
while !handles.is_empty() {
    handles.retain_mut(|h| {
        match h.try_wait() {
            Ok(Some(resp)) => {
                println!("Got response: {}", resp.activation_type);
                false // remove from list
            }
            Ok(None) => true, // still running, keep polling
            Err(e) => {
                eprintln!("Error: {e}");
                false
            }
        }
    });
    std::thread::sleep(std::time::Duration::from_millis(100));
}

Manage notifications

use alerter_rs::Alerter;

// Remove notifications by group
Alerter::remove("updates")?;

// List notifications by group
let info = Alerter::list("updates")?;

// List all
let all = Alerter::list("ALL")?;

API Reference

Alerter

Method Description
new(message) Create a new notification with a message (required)
title(&str) Notification title (default: "Terminal")
subtitle(&str) Notification subtitle
sound(&str) Sound name ("default" for system sound)
actions(Vec<&str>) Action buttons (comma-separated in dropdown if multiple)
dropdown_label(&str) Label for the actions dropdown
reply(&str) Show reply field with placeholder text
close_label(&str) Custom close button label
group(&str) Group ID for notification replacement
sender(&str) Bundle ID to impersonate (default: com.apple.Terminal)
app_icon(&str) URL or path for app icon image
content_image(&str) URL or path for attached image
timeout(u32) Auto-close after N seconds
json(bool) Output as JSON
delay(u32) Deliver after N seconds
at(&str) Deliver at time ("HH:mm" or "yyyy-MM-dd HH:mm")
ignore_dnd(bool) Send even if Do Not Disturb is on
send() Send the notification (blocking), returns Result<AlerterResponse, AlerterError>
send_async() Send the notification (non-blocking), returns Result<NotificationHandle, AlerterError>
remove(group_id) Remove notifications by group ID
list(group_id) List notifications by group ID (or "ALL")

NotificationHandle

Method Description
wait(self) Block until the notification exits, returns Result<AlerterResponse, AlerterError>
try_wait(&mut self) Poll without blocking, returns Ok(None) if still running
detach(self) Consume the handle and let the subprocess continue independently

Dropping a NotificationHandle without calling detach() kills the subprocess.

AlerterResponse

Field Type Description
activation_type String What happened: @CONTENTCLICKED, @CLOSED, @TIMEOUT, or an action label
activation_value Option<String> Reply text (when using reply notifications)

AlerterError

Variant Description
BinaryExtraction(String) Failed to extract the embedded binary
ProcessSpawn(String) Failed to spawn the alerter process
Runtime(String) Alerter returned a non-zero exit code

How it works

  1. Build time: build.rs runs swift build -c release on the vendored alerter/ submodule
  2. Embedding: The compiled binary is included via include_bytes!
  3. Runtime: On first use, the binary is extracted to ~/Library/Caches/alerter_rs/ with a SHA256-based filename for cache invalidation
  4. Invocation: Each API call spawns the cached binary as a subprocess with the appropriate CLI flags

Versioning

The crate version mirrors the bundled alerter version (e.g., alerter v26.5 = crate 26.5.x).

License

This crate wraps alerter by Julien Blanchard (vjeantet), which is licensed under the MIT License.