How Macro's Notification Service Handles Push and In-App Notifications
The Macro platform routes user notifications through dedicated transport adapters for push (APNs) and in-app (WebSocket) delivery, unified by a shared domain model and event-driven architecture.
The Macro codebase implements a Notification Service (services/notification_service) that cleanly separates external push delivery from real-time UI updates. This article examines the service's architecture, data flows, and key source files to explain how both notification channels operate.
Architecture Overview
The service follows a layered design with clear separation between domain logic, transport adapters, and event brokers.
| Layer | Responsibility | Key Files |
|---|---|---|
| API entry point | HTTP routing for CRUD operations | services/notification_service/src/main.rs |
| Domain model | Core Notification struct and enums |
services/notification_service/src/model/notification.rs |
| Transport adapters | Push, email, and in-app delivery modules | Push: notification/send/push/mod.rs; Email: notification/send/email/mod.rs |
| Outbound event broker | Publishes NotificationEvent to message bus |
crates/notification/src/outbound/notification_events.rs |
| In-App consumer | WebSocket forwarding via Tauri extension | crates/notification/src/inbound/notification_events_listener.rs |
| Web UI | React components for notification rendering | apps/web/src/features/notifications/notification-platform.ts |
Both push and in-app flows share the same domain model in model/notification.rs, ensuring consistent data representation while allowing each transport to layer its own metadata.
Push Notification Delivery Flow
1. API Request and Domain Transformation
Client applications POST to /user-notifications (defined in api/user_notification.rs). The service layer transforms this request into an internal notification::domain::models::apple::PushNotificationData struct.
2. Push Adapter Serialization
The notification/send/push/mod.rs adapter converts the internal model into the public PushNotificationData struct:
use notification_service::notification::send::push::PushNotificationData;
use notification_service::notification::domain::models::apple::PushNotificationData as Inner;
// `inner` originates from the Apple-specific domain model
let payload = PushNotificationData::new_from_inner(inner);
// Serialized to JSON and delivered via APNs client
notification_service::notification::send::push::send(payload).await?;
Key fields include notification_id and optional sender_profile_picture_url for rich push styling.
3. External Provider Integration
The APNs client is configured through env.rs/config.rs and delivers to device tokens stored per-user. On successful send, notification_events.rs emits a NotificationDelivered event that the in-app consumer can process for UI synchronization.
In-App Real-Time Notification Flow
1. Event Generation
Any notification creation or status change publishes a NotificationEvent via the outbound broker in crates/notification/src/outbound/notification_events.rs:
use macro_notification::outbound::notification_events::{NotificationEvent, NotificationCreated};
let event = NotificationEvent::Created(NotificationCreated {
notification_id: uuid::Uuid::new_v4(),
user_id: user.id,
title: "New comment".into(),
body: "Someone mentioned you in a document".into(),
// Optional UI grouping fields
});
macro_notification::outbound::notification_events::publish(event).await?;
2. Event Consumption and WebSocket Forwarding
The listener in crates/notification/src/inbound/notification_events_listener.rs subscribes to the broker, deserializes events, and forwards compact JSON payloads over WebSocket connections managed by the Notification Service Extension (apps/web/src/lib/tauri/notification.ts).
3. Client-Side Reception
The web UI maintains a persistent socket connection:
import { NotificationService } from '@/lib/tauri/notification';
const ws = NotificationService.connect();
ws.onMessage((msg) => {
const notification = JSON.parse(msg);
// Update React context, Redux store, or local state
addInAppNotification(notification);
});
React components in apps/web/src/features/notifications/ handle rendering and stacking logic via notification-stacking.ts, supporting collapseKey-based grouping for related notifications.
Shared Domain Model and Transport-Specific Extensions
The unified architecture in Macro's Notification Service prevents data drift between channels. The Notification struct in services/notification_service/src/model/notification.rs defines:
- Type enum: Classification (comment, mention, system)
- Delivery status: Pending, sent, failed, read
- Timestamp tracking: Created, updated, delivered, read
Each transport adapter extends this base with channel-specific metadata:
- Push:
sender_profile_picture_url,badge_count, sound settings - In-App:
collapseKey,priority,expirationfor UI behavior
Key Implementation Files
| Path | Purpose |
|---|---|
services/notification_service/src/main.rs |
Service bootstrap, Axum routing, DI container |
services/notification_service/src/model/notification.rs |
Core domain types and serialization |
services/notification_service/src/notification/send/push/mod.rs |
APNs payload construction and delivery |
services/notification_service/src/notification/send/email/mod.rs |
SMTP/template integration |
crates/notification/src/outbound/notification_events.rs |
Kafka/SQS publishing for NotificationEvent |
crates/notification/src/inbound/notification_events_listener.rs |
Event consumption and WebSocket dispatch |
apps/web/src/lib/tauri/notification.ts |
Tauri WebSocket client for desktop |
apps/web/src/features/notifications/notification-platform.ts |
UI abstraction for notification rendering |
Summary
- Macro's Notification Service centralizes routing logic in
services/notification_servicewhile delegating to transport-specific adapters - Push notifications flow through
notification/send/push/mod.rsto APNs with structuredPushNotificationData - In-app updates use an event-driven bus:
notification_events.rspublishes,notification_events_listener.rsconsumes, and Tauri/WebSocket delivers to React - Both channels share
model/notification.rsfor consistent domain representation - Async Rust handles backend concurrency; TypeScript/React manages real-time UI state
Frequently Asked Questions
How does Macro ensure in-app notifications appear instantly?
The service uses WebSocket connections managed by a Tauri-based Notification Service Extension. When notification_events_listener.rs receives a NotificationEvent from the message bus, it immediately forwards a JSON payload to all connected clients. The React UI subscribes via notification-source.ts and re-renders without polling.
What message broker does Macro use for notification events?
According to the source code in crates/notification/src/outbound/notification_events.rs, the platform supports Kafka or SQS as configurable backends. The outbound module abstracts the specific provider behind a unified publish() interface, allowing environment-based switching.
Can push and in-app notifications be sent simultaneously?
Yes. The service emits a NotificationDelivered event after successful APNs transmission, which the in-app consumer processes. This ensures UI consistency—users see real-time confirmation that their push notification reached the device. Both transports originate from the same domain model in model/notification.rs.
Where is the Apple Push Notification client configured?
APNs credentials and endpoint settings are loaded from environment variables in services/notification_service/src/config.rs (referenced via env.rs). The push adapter in notification/send/push/mod.rs initializes the client at service startup and reuses it for connection pooling.
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 →