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
NSUserNotificationCentervia Objective-C bindings handled intauri::api::notification::macos - Windows: Uses Win32 Toast notifications (
IToastNotification) viatauri::api::notification::windows - Linux: Uses freedesktop.org Desktop Notifications via the
notify-rustcrate intauri::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:
frontend/src-tauri/src/lib.rs: Registers the notification command and sets up the Tauri application shellfrontend/src-tauri/src/audio/recording_manager.rs: Can trigger notifications when recording begins or endsfrontend/src-tauri/src/database/setup.rs: Demonstrates the event emission pattern used for notification callbacksfrontend/src/app/page.tsx: Contains the UI logic for triggering meeting-related alerts
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/notificationto create alerts with titles, bodies, icons, and action buttons - Backend code uses
tauri::api::notification::Notificationin Rust to send alerts from non-UI processes like the recording manager - File locations in Meetily include
frontend/src-tauri/src/lib.rsfor command registration andfrontend/src-tauri/src/audio/recording_manager.rsfor 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-clickedevent
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →