# How PAI Manages Notification Routing with ntfy and Discord

> Learn how PAI manages notification routing using ntfy and Discord. Discover how to customize alert delivery to mobile push or team webhooks via settings.json.

- Repository: [Daniel Miessler 🛡️/Personal_AI_Infrastructure](https://github.com/danielmiessler/personal_ai_infrastructure)
- Tags: how-to-guide
- Published: 2026-02-16

---

**Personal AI Infrastructure (PAI) routes notifications through a flexible, event-driven system that maps event types to ntfy and Discord channels via a configuration object in [`settings.json`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/settings.json), allowing users to customize which alerts reach mobile push or team webhooks.**

The notification routing system in danielmiessler/Personal_AI_Infrastructure enables seamless delivery of alerts from AI agents to mobile devices and team channels. By leveraging a TypeScript-based configuration layer in [`hooks/lib/notifications.ts`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/hooks/lib/notifications.ts), PAI translates runtime events into targeted notifications through ntfy.sh for mobile push and Discord webhooks for team collaboration.

## Configuration-Driven Notification Routing in PAI

### The Default Routing Schema

The core routing logic resides in [`hooks/lib/notifications.ts`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/hooks/lib/notifications.ts) (v2.5), where a `DEFAULT_CONFIG` object defines which events trigger which channels. The system uses a `routing` key that maps event names to arrays of channel identifiers.

```typescript
const DEFAULT_CONFIG: NotificationConfig = {
  // ... other config
  routing: {
    taskComplete: [],                     // voice only
    longTask: ['ntfy'],                  // push for long tasks
    backgroundAgent: ['ntfy'],           // push for background completions
    error: ['ntfy', 'discord'],          // error → ntfy + Discord
    security: ['ntfy', 'discord', 'sms'] // security → all three
  }
};

```

This declarative approach allows the system to route **error** events to both mobile push and team channels simultaneously, while keeping routine **taskComplete** events silent unless explicitly configured otherwise.

### Customizing Routes via settings.json

Users override defaults through [`settings.json`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/settings.json) in the PAI root directory. The configuration loader merges user values with defaults, preserving the routing structure while allowing customization of endpoints and event mappings.

```typescript
const settingsPath = join(paiDir, 'settings.json');
// ... file loading ...
return {
  ...DEFAULT_CONFIG,
  ...settings.notifications,
  ntfy: { ...DEFAULT_CONFIG.ntfy, ...settings.notifications?.ntfy },
  discord: { ...DEFAULT_CONFIG.discord, ...settings.notifications?.discord },
  // ...
};

```

This merge strategy ensures that adding a single key to [`settings.json`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/settings.json) updates the configuration without requiring users to redefine the entire routing table.

## How PAI Dispatches Notifications to ntfy and Discord

The `notify` function in [`hooks/lib/notifications.ts`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/hooks/lib/notifications.ts) serves as the central dispatcher. It accepts an event type, looks up the routing configuration, and delegates to channel-specific sender functions.

```typescript
const channels = config.routing[event] || [];
for (const channel of channels) {
  if (channel === 'ntfy') { /* ... sendPush ... */ }
  if (channel === 'discord') { /* ... sendDiscord ... */ }
  // ...
}

```

### Sending Mobile Push Alerts with ntfy

For **ntfy** integration, the `sendPush` function constructs a POST request to the ntfy.sh server. It supports priority levels, tags, and action buttons through HTTP headers.

```typescript
const url = `https://${config.ntfy.server}/${config.ntfy.topic}`;
const response = await fetch(url, { 
  method: 'POST', 
  headers, 
  body: message 
});

```

The implementation maps PAI's `NotificationPriority` to ntfy's numeric priority scale, ensuring critical alerts bypass quiet hours on mobile devices.

### Posting Rich Embeds to Discord Webhooks

The **Discord** channel uses webhook URLs to post structured messages. The `sendDiscord` function supports both simple text payloads and rich embeds with colors, fields, and timestamps.

```typescript
const response = await fetch(config.discord.webhook, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload)
});

```

Rich embeds allow security alerts to appear with red color coding and inline fields for IP addresses or user IDs, while routine notifications use minimal text formatting.

## Event-Driven Notification Flow in PAI

The notification system integrates with PAI's hook architecture. When AI agents complete tasks or encounter errors, they emit events that trigger the routing logic.

1. **Session Initialization**: `recordSessionStart()` creates a temporary file tracking the session start time.
2. **Event Emission**: Hooks like `notifyError` or `notifyBackgroundAgent` call `notify(event, ...)` with context-specific data.
3. **Routing Resolution**: The `notify` function queries `config.routing[event]` to determine active channels.
4. **Channel Dispatch**: Selected channels execute their respective `fetch` calls to ntfy or Discord endpoints.
5. **Graceful Failure**: Each channel wrapper includes error handling that logs failures without interrupting the main AI workflow.

This pipeline ensures that a **security** event simultaneously reaches a mobile device via ntfy and a team channel via Discord, while a routine **taskComplete** remains silent unless explicitly configured otherwise.

## Practical Configuration Examples

### Enabling Dual-Channel Error Alerts

To route error notifications to both ntfy and Discord, modify [`settings.json`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/settings.json):

```json
{
  "notifications": {
    "ntfy": {
      "enabled": true,
      "topic": "my-pai-alerts",
      "server": "ntfy.sh"
    },
    "discord": {
      "enabled": true,
      "webhook": "https://discord.com/api/webhooks/XXXXXXXX/XXXXXXXX"
    },
    "routing": {
      "error": ["ntfy", "discord"]
    }
  }
}

```

### Triggering Notifications from Custom Hooks

Import the notification library in custom hook implementations:

```typescript
import { notifyError, notifyBackgroundAgent } from '../hooks/lib/notifications.js';

async function someCriticalOperation() {
  try {
    // ... do work ...
  } catch (e) {
    await notifyError(`Operation failed: ${e.message}`, {
      title: 'Critical Failure',
      priority: 'high',
      tags: ['warning', 'x']
    });
  }
}

```

### Sending Direct ntfy Push Notifications

For ad-hoc mobile alerts outside the routing system:

```typescript
import { sendPush } from '../hooks/lib/notifications.js';

await sendPush('Backup completed', {
  title: 'PAI Backup',
  priority: 'default',
  tags: ['white_check_mark', 'package']
});

```

### Posting Rich Discord Embeds

For detailed team notifications with formatting:

```typescript
import { sendDiscord } from '../hooks/lib/notifications.js';

await sendDiscord('Security alert detected', {
  title: '⚠️ Security Alert',
  description: 'Unexpected login from IP 203.0.113.42',
  color: 0xff0000,
  fields: [{ name: 'User', value: 'alice', inline: true }]
});

```

## Summary

PAI's notification routing system provides a robust, configuration-driven bridge between AI agent events and external communication channels. Key takeaways include:

- **Declarative Routing**: The `routing` object in [`settings.json`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/settings.json) maps event types like `error` and `security` to arrays of channels including `ntfy` and `discord`.
- **Dual-Channel Delivery**: The system simultaneously dispatches to multiple channels, enabling mobile alerts via ntfy.sh while posting detailed embeds to Discord webhooks.
- **Graceful Degradation**: Channel failures are caught and logged without interrupting AI workflows, ensuring notifications never block critical operations.
- **Extensible Architecture**: New channels can be added by extending the `NotificationConfig` interface and implementing corresponding sender functions in [`hooks/lib/notifications.ts`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/hooks/lib/notifications.ts).

## Frequently Asked Questions

### How do I enable both ntfy and Discord notifications for security alerts?

Configure the `routing` object in your [`settings.json`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/settings.json) to include both channels for the `security` event type. Set the `ntfy` configuration with your topic and server, and provide your Discord webhook URL. When a security event triggers, PAI automatically invokes both `sendPush` and `sendDiscord` functions according to the routing configuration.

### What happens if the Discord webhook is unreachable?

The notification system wraps each channel call in a try-catch block. If the Discord webhook returns a network error or HTTP failure, the `sendDiscord` function logs the error but does not throw an exception. The main AI workflow continues uninterrupted, and any other configured channels (like ntfy) still attempt delivery.

### Can I route different event types to different channels?

Yes. The `routing` configuration supports granular event-to-channel mapping. You can send `longTask` completions only to ntfy for mobile awareness, while routing `error` events to both ntfy and Discord for immediate team visibility. Each event type accepts an array of channel identifiers defined in your configuration.

### Where is the notification configuration loaded from?

PAI loads notification settings from [`settings.json`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/settings.json) in the project root, specifically merging the `notifications` key with internal defaults defined in [`hooks/lib/notifications.ts`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/hooks/lib/notifications.ts). The configuration loader uses a spread operator to combine default values with user overrides, ensuring that partial customizations (like adding a Discord webhook without redefining ntfy settings) work correctly.