Notification Channels
Push agent events to Discord, Telegram, and WhatsApp. The notification system uses a pluggable adapter architecture — each platform is a separate adapter behind a unified dispatcher.
Architecture
Agent Events (agent_end, tool errors, quality gates)
│
▼
NotificationDispatcher
├── fan-out to all registered adapters
├── retry with exponential backoff
├── rate limiting (global + per-channel)
└── dead letter queue
│
┌────┼────┐
▼ ▼ ▼
Discord Telegram WhatsAppQuick Start
import { createNotificationExtension } from '@mariozechner/pi-coding-agent/extensions/nity-notifications';
const notifications = await createNotificationExtension({
enabled: true,
defaultPriority: 'normal',
channels: {
discord: {
enabled: true,
mode: 'webhook',
webhookUrl: 'ENV:DISCORD_WEBHOOK_URL',
},
},
});
// Send a notification
await notifications.sendText('Build completed successfully');
// Send with priority and target channels
await notifications.sendText(
'Quality gate failed',
'critical',
['discord:build-alerts']
);
// Shutdown
await notifications.shutdown();Supported Platforms
| Platform | Mode | Bidirectional | Priority |
|---|---|---|---|
| Discord | Webhook | No (outbound only) | Immediate |
| Telegram | Bot API (long-polling) | Yes (replies + buttons) | Immediate |
| Cloud API (Meta) | Yes (webhook) | P2 — requires Meta verification |
Discord
Webhook-only — no bot needed. Single HTTP POST per notification with embed support and priority color coding.
channels: {
discord: {
enabled: true,
mode: 'webhook',
webhookUrl: 'https://discord.com/api/webhooks/123/abc',
botName: 'Agent', // optional
avatarUrl: 'https://...', // optional
},
}Telegram
Bot API with long-polling (no HTTP server needed). Supports inline keyboard buttons for interactive notifications and bidirectional message handling.
channels: {
telegram: {
enabled: true,
mode: 'long-polling',
botToken: 'ENV:TELEGRAM_BOT_TOKEN',
defaultChatId: 'ENV:TELEGRAM_CHAT_ID',
parseMode: 'MarkdownV2',
},
}Telegram adapter automatically handles MarkdownV2 escaping and supports inline keyboard buttons from ActionButton definitions.
Cloud API (Meta Business Platform). Supports text, media, interactive buttons, and template messages.
channels: {
whatsapp: {
enabled: true,
mode: 'cloud-api',
phoneNumberId: 'ENV:WHATSAPP_PHONE_ID',
phoneNumber: 'ENV:WHATSAPP_PHONE',
accessToken: 'ENV:WHATSAPP_TOKEN',
appSecret: 'ENV:WHATSAPP_SECRET',
webhookVerifyToken: 'ENV:WHATSAPP_VERIFY_TOKEN',
defaultRecipient: 'ENV:WHATSAPP_RECIPIENT',
},
}WhatsApp requires Meta Business verification. Use Discord or Telegram for immediate setup.
Event Mapping
Agent lifecycle events automatically map to notifications:
| Agent Event | Priority | Content |
|---|---|---|
agent_end | normal | Task completed summary |
tool_execution_end (error) | high | Tool name + error message |
quality_gate (fail) | critical | Gate details + blocking issues |
session_shutdown | low | Session ended |
Configuration
# Discord
export DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/..."
# Telegram
export TELEGRAM_BOT_TOKEN="123456:ABC-DEF"
export TELEGRAM_CHAT_ID="123456789"
# WhatsApp
export WHATSAPP_PHONE_ID="123456789"
export WHATSAPP_PHONE="+1234567890"
export WHATSAPP_TOKEN="EAAxxxxx"
export WHATSAPP_SECRET="your-app-secret"
export WHATSAPP_VERIFY_TOKEN="your-verify-token"
export WHATSAPP_RECIPIENT="+1234567890"Resilience
Retry with Backoff
Failed sends are retried with exponential backoff (configurable):
maxAttempts: 3 (default)initialDelayMs: 1000maxDelayMs: 30000backoffMultiplier: 2jitterMs: 500
Dead Letter Queue
Notifications that fail after all retries are persisted to the DLQ:
- Content is redacted (truncated to 100 chars, metadata stripped) for security
- JSONL persistence support
- Manual retry:
dispatcher.retryDLQ(index) - Manual purge:
dispatcher.purgeDLQ()
Rate Limiting
Global and per-channel rate limits prevent platform API throttling:
- Global: 20 requests/second (configurable)
- Per-channel: configurable per platform (default: Discord 5/s, Telegram 25/s, WhatsApp 10/s)
Data Types
Notification
interface Notification {
id: string; // UUID v7
timestamp: string; // ISO 8601
priority: 'low' | 'normal' | 'high' | 'critical';
channels?: string[]; // target channels (empty = all)
content: NotificationContent;
metadata?: Record<string, unknown>;
}
interface NotificationContent {
text: string; // plain-text fallback (required)
markdown?: string; // rich text
media?: MediaAttachment[]; // images, video, audio, documents
actions?: ActionButton[]; // interactive buttons
}DeliveryReceipt
interface DeliveryReceipt {
notificationId: string;
channelId: string;
status: 'accepted' | 'sent' | 'delivered' | 'read' | 'failed';
platformMessageId?: string;
error?: { code: string; message: string };
timestamp: string;
}Security
- Secrets loaded from environment variables via
ENV:prefix in config - WhatsApp webhook signature verified with
crypto.timingSafeEqual()(timing-safe) - DLQ content redacted before persistence (no secret leakage)
- No hardcoded credentials anywhere in the codebase
All adapter files are under 400 lines. The notification extension follows the same component size directive as the rest of the codebase.
Related
- Event Streaming — subscribe to agent events
- Quick Start — bridge SDK overview
- Cost Tiers — model configuration