How to Implement Cross-Platform System Notifications in a Tauri App

Tauri provides a unified JavaScript and Rust API that abstracts native notification systems across macOS, Windows, and Linux, allowing you to send system-level alerts from your frontend or backend code without platform-specific dependencies.

Meetily, an open-source meeting recorder built with Tauri, leverages this architecture to deliver privacy-first desktop notifications. By using the @tauri-apps/api/notification module on the frontend and tauri::api::notification on the Rust backend, you can implement native system alerts that work consistently across all supported operating systems while keeping all data processing on-device.

How Tauri Bridges Native Notification Systems

Tauri delegates notification rendering to each operating system's native APIs through a centralized bridge. When you invoke a notification from JavaScript, Tauri serializes the request via IPC and dispatches it through the appropriate platform-specific implementation on the Rust side.

The platform mapping works as follows:

  • macOS: Uses NSUserNotificationCenter via Objective-C bindings handled in tauri::api::notification::macos
  • Windows: Uses Win32 Toast notifications (IToastNotification) via tauri::api::notification::windows
  • Linux: Uses freedesktop.org Desktop Notifications via the notify-rust crate in tauri::api::notification::linux

This architecture ensures zero external service dependencies, preserving user privacy by generating notifications entirely on-device.

Frontend Implementation with TypeScript

Sending Notifications from the UI

Import the Notification constructor from @tauri-apps/api/notification to create alerts directly from your React or TypeScript code. In Meetily's frontend/src/app/page.tsx, you can implement meeting-specific notifications like this:

import { Notification } from '@tauri-apps/api/notification';

export const showMeetingStarted = async (meetingName: string) => {
  await new Notification(`🗓️ ${meetingName}`, {
    body: 'The meeting has begun – recording is active.',
    icon: 'icons/meeting.png',
    actions: [{ identifier: 'open', title: 'Open Meetily' }],
  });
};

The Notification constructor accepts a title string and an options object that supports body, icon, and actions parameters. These map directly to native notification capabilities on each platform.

Handling User Actions

To respond when users click notification buttons, use Tauri's event listener system:

import { listen } from '@tauri-apps/api/event';

listen('notification-clicked', (event) => {
  const { identifier } = event.payload as { identifier: string };
  if (identifier === 'open') {
    window.location.href = '/';
  }
});

This pattern allows the UI to react to notification interactions even when the application window is not focused.

Backend Implementation in Rust

Creating a Notification Helper

For Rust-side code paths (such as the recording manager), create a centralized helper in frontend/src-tauri/src/notifications.rs:

use tauri::{AppHandle, Manager};
use tauri::api::notification::Notification;

/// Sends a system notification from Rust (useful for non‑UI code paths)
pub async fn notify_meeting_started(app: &AppHandle, name: &str) -> Result<(), tauri::Error> {
    Notification::new("Meeting Started")
        .body(&format!("{} is now recording.", name))
        .icon("icons/meeting.png")
        .show(app)?;
    Ok(())
}

The Rust API returns a Result<(), tauri::Error> that can be awaited from the frontend, allowing you to handle permission failures or system errors gracefully.

Registering Commands

Expose the notification functionality to your frontend by registering a command in frontend/src-tauri/src/lib.rs:

#[tauri::command]
async fn start_meeting_notification(
    app: tauri::AppHandle, 
    meeting_name: String
) -> Result<(), String> {
    notifications::notify_meeting_started(&app, &meeting_name)
        .await
        .map_err(|e| e.to_string())
}

Then invoke this command from TypeScript:

import { invoke } from '@tauri-apps/api/tauri';

await invoke('start_meeting_notification', { meetingName: 'Team Standup' });

Integration Points in Meetily

The notification system integrates with Meetily's existing architecture at these key locations:

By placing notification logic in src-tauri/src/notifications.rs, you create a reusable module that both the audio recording manager and UI components can access.

Platform-Specific Considerations

macOS Permission Requirements

Before sending the first notification on macOS, you must request User Notification permissions. While Tauri handles the notification display, you need to ensure your Info.plist contains the appropriate permissions, or use tauri-plugin-os to query authorization status before calling the notification API.

Windows and Linux Support

Windows automatically supports Toast notifications without additional configuration. Linux requires a notification daemon supporting the freedesktop.org specification (present in GNOME, KDE, and most modern distributions). The notify-rust crate handles the D-Bus communication automatically.

All platforms support the async-friendly pattern shown above, ensuring non-blocking notification delivery that won't freeze your UI during the IPC roundtrip.

Summary

  • Tauri's notification API abstracts macOS NSUserNotificationCenter, Windows Toast notifications, and Linux Desktop Notifications behind a single interface
  • Frontend code uses @tauri-apps/api/notification to create alerts with titles, bodies, icons, and action buttons
  • Backend code uses tauri::api::notification::Notification in Rust to send alerts from non-UI processes like the recording manager
  • File locations in Meetily include frontend/src-tauri/src/lib.rs for command registration and frontend/src-tauri/src/audio/recording_manager.rs for integration triggers
  • Permission handling is required on macOS but automatic on Windows and Linux
  • Event listeners capture user interactions with notification buttons via the notification-clicked event

Frequently Asked Questions

Do I need to install additional dependencies for Tauri notifications?

No. Notification support is built into the core Tauri library. Both @tauri-apps/api/notification for the frontend and tauri::api::notification for the backend are available without adding extra crates or npm packages. The notify-rust crate used for Linux support is bundled with Tauri's core dependencies.

Can I send notifications when the Tauri window is closed?

Yes, but only if your Tauri application is configured to run in the background. Notifications require the Rust process to remain active. You can keep the process running by preventing exit in your tauri::Builder setup or by using the system tray plugin, then calling the notification commands from the backend even when windows are closed.

How do I handle notification permission errors on macOS?

Wrap your notification calls in try-catch blocks (JavaScript) or match on the Result type (Rust). If permissions are denied, Tauri will return an error that you can handle gracefully. You can check permission status beforehand using tauri-plugin-os or by attempting to send a test notification and catching the specific permission-denied error variant.

What's the difference between frontend and backend notification calls?

Frontend notifications (using new Notification()) execute immediately in the browser context and are suitable for UI-driven events. Backend notifications (using Notification::new().show()) execute in the Rust runtime and are ideal for background processes like the recording manager in frontend/src-tauri/src/audio/recording_manager.rs. Both ultimately use the same native APIs, but backend calls can be triggered from anywhere in your Rust code, not just the UI thread.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →