# How Notifications Are Sent and Managed Within CasaOS: A Technical Deep Dive

> Discover how CasaOS sends and manages notifications. Learn about its technical details involving SQLite, MessageBus, and real-time WebSocket delivery for seamless updates.

- Repository: [IceWhale/CasaOS](https://github.com/IceWhaleTech/CasaOS)
- Tags: deep-dive
- Published: 2026-06-27

---

**CasaOS persists notifications to a SQLite database via the `AppNotify` model, publishes events through an internal MessageBus, and delivers them to front-end clients using WebSocket or Socket.io connections.**

The notification subsystem in IceWhaleTech/CasaOS provides a lightweight, decoupled architecture for alerting users about system events, app installations, and file operations. Understanding how notifications are sent and managed within CasaOS requires examining the interplay between the persistence layer, the core service implementation, and the real-time transport mechanisms.

## Notification Data Model and Persistence

All notifications are stored in a local SQLite database using GORM for object-relational mapping.

### The AppNotify Model

The data structure is defined in [`service/model/app_notify.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/service/model/app_notify.go) as `model.AppNotify`. This struct maps directly to the `app_notify` table and contains fields for `Id`, `Class`, `State`, `Title`, `Content`, `CustomId`, and `CreatedAt`.

The database schema is initialized in [`pkg/sqlite/db.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/pkg/sqlite/db.go) through GORM auto-migration:

```go
db.AutoMigrate(&model2.AppNotify{}, …)

```

### Database State Management

Notifications maintain a state lifecycle through the `State` field, utilizing constants defined in [`types/notify.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/types/notify.go) such as `NOTIFY_UNREAD` and `NOTIFY_READ`. The `Class` field categorizes notifications (e.g., `types.NOTIFY_APP` for application-related alerts), allowing the UI to filter messages by type.

## Core Notification Service

The business logic resides in [`service/notify.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/service/notify.go), which implements the `NotifyServer` interface through the `notifyServer` struct. This service holds a GORM database instance and a concurrent map (`SystemTempMap`) for transient system-wide data.

### Key Service Methods

The `notifyServer` provides several atomic operations for notification management:

- **`AddLog`** – Persists a new `AppNotify` record to the SQLite database.
- **`UpdateLog`** / **`UpdateLogByCustomID`** – Modifies existing notifications by primary key or custom identifier.
- **`GetList(c int)`** – Retrieves unread notifications filtered by class (e.g., `types.NOTIFY_APP`).
- **`MarkRead(id, state)`** – Updates the notification state from unread to read.
- **`SendNotify(name, message)`** – Publishes a JSON-encoded event to the internal MessageBus.
- **`SendFileOperateNotify`** – Broadcasts periodic updates about file-operation queue status.

### MessageBus Integration

When `SendNotify` is invoked, the service publishes to `common.SERVICENAME` using the MessageBus client:

```go
response, err := MyService.MessageBus().
    PublishEventWithResponse(context.Background(),
    common.SERVICENAME, name, msg)

```

If the HTTP response status is not 200 OK, the service logs an error (lines 62–68 in [`service/notify.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/service/notify.go)). This decouples notification creation from delivery, allowing the front end to subscribe to specific event channels without polling the database directly.

## HTTP API Layer

The REST API façade is implemented in [`route/v1/notify.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/route/v1/notify.go) (current API) and [`route/v1/notify_old.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/route/v1/notify_old.go) (legacy WebSocket fallback).

### REST Endpoints

The Gin-based handlers delegate directly to the `NotifyServer` implementation:

- **`POST /v1/notify/:path`** – `PostNotifyMessage` forwards the request body to `Notify().SendNotify()`, triggering a MessageBus event.
- **`POST /v1/notify/system_status`** – `PostSystemStatusNotify` stores transient system metrics via `SettingSystemTempData`.
- **`PUT /v1/notify/:id`** – `PutNotifyRead` marks a specific notification as read by calling `MarkRead`.

### Legacy WebSocket Fallback

[`route/v1/notify_old.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/route/v1/notify_old.go) handles raw WebSocket connections via the `NotifyWS` handler. The handler upgrades HTTP requests and stores connections in `service.WebSocketConns`. A background goroutine (`SendMeg()`) continuously pulls unread notifications, marshals them to JSON, and pushes them to each connected client, marking messages as read after successful transmission.

## Real-Time Delivery Mechanisms

CasaOS supports dual transport protocols for real-time updates:

1. **Socket.io via MessageBus** – The preferred method for modern clients, where `SendNotify` publishes to named channels like `casaos:system:utilization`.
2. **Raw WebSocket** – Legacy clients connect through `NotifyWS` and receive batched unread notifications via the `SendMeg()` background worker.

## Periodic System Status Notifications

The [`route/periodical.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/route/periodical.go) task retrieves temporary system metrics and broadcasts utilization data:

```go
systemTempMap := service.MyService.Notify().GetSystemTempMap()
service.MyService.Notify().SendNotify("casaos:system:utilization", body)

```

This feeds dashboard widgets displaying CPU and memory usage without requiring persistent database entries.

## Code Examples

### Creating a Notification Programmatically

```go
notif := model.AppNotify{
    Class:   types.NOTIFY_APP,
    State:   types.NOTIFY_UNREAD,
    Title:   "Backup finished",
    Content: "Your backup completed successfully.",
}
service.MyService.Notify().AddLog(notif)

```

### Sending a Custom Event via MessageBus

```go
payload := map[string]interface{}{
    "title":   "Disk space low",
    "message": "Only 5GB left on /dev/sda1",
}
service.MyService.Notify().SendNotify("casaos:disk:alert", payload)

```

### Retrieving Unread Notifications

```go
unread := service.MyService.Notify().GetList(types.NOTIFY_APP)
for _, n := range unread {
    fmt.Printf("🔔 %s – %s\n", n.Title, n.Content)
}

```

### Triggering via REST API

```bash
curl -X POST http://localhost:80/api/v1/notify/app \
    -H "Content-Type: application/json" \
    -d '{"title":"New version","content":"CasaOS 2.0 is now available"}'

```

## Summary

- **Persistence layer**: Notifications are stored as `AppNotify` records in SQLite, auto-migrated via [`pkg/sqlite/db.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/pkg/sqlite/db.go).
- **Service layer**: [`service/notify.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/service/notify.go) implements `NotifyServer` with methods for CRUD operations and MessageBus publishing.
- **Transport layer**: Events are delivered via Socket.io (through MessageBus) or legacy WebSocket connections managed in [`route/v1/notify_old.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/route/v1/notify_old.go).
- **API layer**: REST endpoints in [`route/v1/notify.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/route/v1/notify.go) provide external triggers for creating notifications and marking them read.
- **Transient data**: System metrics use `SystemTempMap` and are broadcast periodically from [`route/periodical.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/route/periodical.go).

## Frequently Asked Questions

### How does CasaOS store notification data permanently?

CasaOS uses GORM to persist notifications in a SQLite database. The `AppNotify` struct in [`service/model/app_notify.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/service/model/app_notify.go) defines the schema, and [`pkg/sqlite/db.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/pkg/sqlite/db.go) executes `AutoMigrate` to create the `app_notify` table during initialization. Each notification includes metadata fields like `Class`, `State`, and `CustomId` to support filtering and lifecycle management.

### What is the difference between MessageBus and WebSocket delivery?

The **MessageBus** (`common.SERVICENAME`) is the primary event broker used by `SendNotify()` to publish JSON payloads to Socket.io channels, enabling decoupled, channel-based subscriptions. The **WebSocket** implementation in [`route/v1/notify_old.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/route/v1/notify_old.go) is a legacy fallback that maintains persistent connections and pushes batched unread notifications via the `SendMeg()` goroutine. Modern implementations should prefer the MessageBus approach.

### How can external services trigger notifications in CasaOS?

External services can POST to `/v1/notify/:path` to trigger notifications. The `PostNotifyMessage` handler in [`route/v1/notify.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/route/v1/notify.go) validates the request and invokes `Notify().SendNotify()`, which publishes the event to the MessageBus and persists the record if configured. This allows CLI tools, scripts, or third-party apps to integrate with the CasaOS notification system without direct database access.

### Where are temporary system metrics like CPU usage stored before being broadcast?

Transient system metrics are stored in the `SystemTempMap` concurrent map within the `notifyServer` struct. The `SettingSystemTempData` method stores values, and `GetSystemTempMap` retrieves them. The periodic task in [`route/periodical.go`](https://github.com/IceWhaleTech/CasaOS/blob/main/route/periodical.go) reads this map and broadcasts utilization data via `SendNotify("casaos:system:utilization", body)`, ensuring the dashboard displays real-time statistics without database writes.