How PAI Manages Notification Routing with ntfy and Discord
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, 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, 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 (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.
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 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.
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 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 serves as the central dispatcher. It accepts an event type, looks up the routing configuration, and delegates to channel-specific sender functions.
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.
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.
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.
- Session Initialization:
recordSessionStart()creates a temporary file tracking the session start time. - Event Emission: Hooks like
notifyErrorornotifyBackgroundAgentcallnotify(event, ...)with context-specific data. - Routing Resolution: The
notifyfunction queriesconfig.routing[event]to determine active channels. - Channel Dispatch: Selected channels execute their respective
fetchcalls to ntfy or Discord endpoints. - 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:
{
"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:
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:
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:
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
routingobject insettings.jsonmaps event types likeerrorandsecurityto arrays of channels includingntfyanddiscord. - 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
NotificationConfiginterface and implementing corresponding sender functions inhooks/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 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 in the project root, specifically merging the notifications key with internal defaults defined in 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.
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 →