An intelligent notification routing system that receives webhooks from various sources (Linear, Sentry, etc.) and uses AI to determine the most appropriate channels for delivery. The system analyzes notification content and routes messages to predefined email channels based on context, priority, and team relevance.
Instead of broadcasting all notifications to everyone, this system:
- Receives webhooks from external services
- Analyzes the content using AI (via OpenRouter with multiple model options)
- Routes notifications to appropriate channels based on:
- Content analysis and keywords
- Priority/severity levels
- Team assignments
- Predefined channel descriptions
- Delivers formatted, actionable messages via email
Each notification includes:
- Clear summary of what happened
- Relevant links in proper format
- Context tailored to the target audience
- Action items when applicable
This system uses OpenRouter as the AI provider, giving access to multiple AI models with intelligent model selection based on notification complexity and cost optimization.
The system automatically selects the most appropriate AI model based on the notification characteristics:
- Fast Model (
openai/gpt-oss-20b
): Simple notifications with low severity and minimal description - ultra-cheap at $0.10/$0.50 per million tokens - Balanced Model (
anthropic/claude-3.5-sonnet
): Standard triage operations (default) - Advanced Model (
openai/gpt-4o
): Critical issues requiring sophisticated analysis - Reasoning Model (
moonshotai/kimi-k2
): Complex scenarios with multiple factors, excellent for tool use and coding
Model | Provider | Use Case | Cost (per 1M tokens) | Max Tokens | Context |
---|---|---|---|---|---|
GPT-OSS 20B | OpenAI | Fast | $0.10/$0.50 | 131K | Open-weight MoE |
Claude 3.5 Sonnet | Anthropic | Balanced | $3/$15 | 8K | Best overall |
GPT-4o Mini | OpenAI | Fast | $0.15/$0.60 | 16K | Fallback fast |
GPT-4o | OpenAI | Advanced | $5/$15 | 4K | Complex analysis |
Kimi K2 Instruct | MoonshotAI | Reasoning | $1/$3 | 131K | Tool use expert |
Claude 3 Opus | Anthropic | Reasoning | $15/$75 | 4K | Most capable |
Gemini Pro 1.5 | Balanced | $1.25/$5 | 8K | Fallback balanced |
This system is built around the principle of AI transparency and debuggability. We use structured data formatting and comprehensive logging to make AI decision-making visible and debuggable.
We use the @zenbase/llml
library to convert JavaScript objects into clean, XML-like formatted strings. This approach provides several benefits:
- Human-readable logs: Complex webhook payloads become easy to scan and understand
- AI-friendly format: Structured data that AI models can easily parse and reason about
- Consistent formatting: All logged objects follow the same clear structure
- Debugging efficiency: Quickly identify issues in webhook data or AI responses
Our formatting utilities provide:
// Convert any object to formatted XML-like string
const formatted = stringifyForLogging(webhookPayload);
// Log with clear separators and labels
logFormatted(payload, "Linear Webhook Received");
// Handle large objects with truncation
const truncated = stringifyTruncated(largeObject, 1000);
Key Functions:
stringifyForLogging(obj)
- Primary formatter using llml with JSON fallbacklogFormatted(obj, label)
- Adds clear separators and labels for log scanningstringifyTruncated(obj, maxLength)
- Prevents log overflow with large payloads
Why This Matters:
- AI Debugging: When AI makes routing decisions, we can see exactly what data it analyzed
- Webhook Debugging: Complex nested webhook payloads become immediately readable
- Error Tracking: Failed AI responses are logged in structured format for analysis
- Performance Monitoring: Easy to spot patterns in notification types and routing decisions
This structured approach to logging and data formatting makes the entire AI pipeline transparent and debuggable, which is crucial for a system that makes automated decisions about important notifications.
├── CONFIGURATION.ts # Channel definitions, webhook config, AI prompts, OpenRouter models
├── ai-service.ts # OpenRouter AI service with multi-model support
├── ai-model-test.ts # Testing utility for AI models and performance
├── citation-context.ts # Citation utilities for AI prompting with links
├── linear-webhook.ts # HTTP webhook handler for Linear (with GET config page)
├── notification-triage.ts # Shared AI triage system for all notification sources
├── sentry-webhook.ts # HTTP webhook handler for Sentry (with GET config page)
├── stringify-utils.ts # LLML-based object formatting utilities for AI debugging
├── main.tsx # (Reserved for future integrations)
└── README.md # This file
- Webhook Handler: Secure endpoint with HMAC-SHA256 signature verification
- Configuration Page: Visit the webhook URL in browser for setup instructions
- Event Filtering: Skips low-value events (views, reads)
- Rich Data Processing: Handles titles, descriptions, assignees, labels, priorities, teams
- Async Processing: Webhooks respond immediately to prevent timeouts, processing happens asynchronously
- Error Monitoring: Processes error events and issue notifications
- Severity Mapping: Converts Sentry levels (fatal, error, warning, etc.) to standard severity
- Stack Trace Processing: Extracts meaningful error context and location information
- Environment Awareness: Routes based on production vs staging environments
- Timeout Prevention: Fast response with background processing to avoid Linear's 4-retry timeout behavior
- Multi-Model Support: Access to Claude, GPT-4, Gemini, and other leading AI models
- Intelligent Model Selection: Automatically chooses optimal model based on notification complexity
- Cost Optimization: Uses faster, cheaper models for simple notifications
- Fallback Support: Automatic failover to alternative models if primary fails
- Usage Tracking: Detailed logging of token usage and estimated costs
- Configurable Models: Easy to add new models or adjust selection criteria
- Timeout Protection: 25-second timeout on AI requests to prevent webhook timeouts
- Unified Interface: All webhook sources convert to standardized
NotificationData
format - Citation Context: Advanced AI prompting with proper link generation and ID replacement
- Fallback Logic: Intelligent routing even when AI fails, based on source and priority
- Enhanced Metadata: Tracks urgency, suggested actions, and formatted summaries
- Link Generation: Automatic creation of proper URLs and citations in AI responses
- ID Replacement: Converts UUIDs and IDs to readable citation keys for better AI understanding
- Structured References: Maintains mapping between citation keys and actual resources
- Markdown Output: Generates properly formatted links in final notifications
- Multi-Channel Support: Can notify multiple channels for critical issues
- Rich Formatting: HTML emails with structured information
- Branded Messages: Consistent formatting with clear subject prefixes
- Error Handling: Graceful failure handling per channel
Pre-configured channels include:
- Engineering Critical: P0/P1 issues, outages, security incidents
- Engineering General: Feature development, code reviews, technical debt
- Product Team: UX issues, feature requests, business logic
- DevOps & Infrastructure: Deployment, monitoring, performance
- Quality Assurance: Testing, automation, bug reports
- General Notifications: Low-priority and uncategorized items
Set these in your Val Town environment:
LINEAR_WEBHOOK_SECRET
- Your Linear webhook signing secretSENTRY_WEBHOOK_SECRET
- Your Sentry webhook client secretOPENROUTER_API_KEY
- Your OpenRouter API key (get from https://openrouter.ai/)
Update email addresses in CONFIGURATION.ts
to match your organization:
{
id: 'engineering-critical',
name: 'Engineering Critical',
email: 'your-critical-team@company.com', // Update this
description: '...',
// ...
}
- Visit your Linear webhook URL in a browser to see configuration instructions
- Copy the webhook URL from the configuration page
- In Linear: Settings → API → Webhooks → Create webhook
- Paste the URL and copy the signing secret
- Add the signing secret to Val Town as
LINEAR_WEBHOOK_SECRET
- Select events to monitor (Issues, Comments, Projects recommended)
- Visit your Sentry webhook URL in a browser to see configuration instructions
- Copy the webhook URL from the configuration page
- In Sentry: Settings → Developer Settings → Internal Integrations
- Create new integration or edit existing one
- Add the webhook URL and copy the client secret
- Add the client secret to Val Town as
SENTRY_WEBHOOK_SECRET
- Enable permissions and subscribe to Error/Issue events
Use the built-in testing utility to verify your OpenRouter setup:
import runTests from './ai-model-test.ts';
await runTests(); // Test all models and use cases
Or test individual components:
import { aiService } from './ai-service.ts';
import { testModel, showModelInfo } from './ai-model-test.ts';
// Show available models and pricing
await showModelInfo();
// Test a specific model
await testModel('anthropic/claude-3.5-sonnet', 'Hello, test message');
// Test notification triage scenario
await testTriageScenario();
Update model selection in CONFIGURATION.ts
if needed:
export const AI_CONFIG = {
modelSelection: {
default: 'anthropic/claude-3.5-sonnet', // Change default model
fast: 'openai/gpt-oss-20b', // Ultra-cheap for simple notifications
advanced: 'openai/gpt-4o', // For critical analysis
reasoning: 'moonshotai/kimi-k2' // For complex scenarios with tool use
}
};
New Models Added:
- GPT-OSS 20B: Open-weight model with 131K context, extremely cost-effective at $0.10/$0.50 per million tokens
- Kimi K2 Instruct: 1T parameter MoE model optimized for coding, reasoning, and tool use with 131K context
- Linear: Create or update an issue to test the integration
- Sentry: Trigger an error in your application to test the integration
- AI Models: Use
ai-model-test.ts
to verify OpenRouter connectivity - Check Val Town logs for debugging information
- Receive: Linear sends webhook with HMAC signature
- Verify: Validate signature using signing secret
- Parse: Extract relevant data (title, description, priority, labels, etc.)
- Respond: Return 200 OK immediately to prevent timeout (Linear retries 4 times on timeout)
- Analyze: Send to AI via OpenRouter with intelligent model selection (async)
- Route: AI selects appropriate channel(s) with reasoning (async)
- Format: Create tailored message for target audience (async)
- Deliver: Send email notification(s) (async)
- Log: Record success/failure, token usage, and costs for debugging (async)
The system prevents webhook timeouts through several mechanisms:
- Immediate Response: Webhook returns 200 OK within milliseconds
- Async Processing: AI analysis happens in background after response
- Fast Default Model: Uses GPT-4o Mini instead of Claude 3.5 Sonnet for speed
- Reduced Token Limits: 400 tokens max instead of 800 for faster generation
- AI Request Timeout: 25-second timeout on AI calls to prevent hanging
- Optimized Model Selection: Uses fast models for critical issues to ensure quick processing
The system automatically chooses the best model for each notification, optimized for speed to prevent timeouts:
// Simple notification → Ultra-fast model (GPT-OSS 20B)
if (severity === 'low' && !description) useCase = 'fast';
// Critical issue → Fast model (GPT-OSS 20B) - changed from advanced for speed
else if (severity === 'critical' || priority === 1) useCase = 'fast';
// Complex scenario → Balanced model (GPT-4o Mini) - changed from reasoning for speed
else if (labels.length > 3 || description.length > 500) useCase = 'balanced';
// Default → Balanced model (GPT-4o Mini) - changed from Claude for speed
else useCase = 'balanced';
All requests are configured to use the Groq provider through OpenRouter for optimal pricing and performance:
provider: {
order: ["groq"] // Forces routing through Groq for better prices
}
The AI considers:
- Keywords: Matches against channel-specific keywords
- Priority: Routes high-priority items to critical channels
- Team Context: Considers team assignments and technical domains
- Content Analysis: Analyzes descriptions for technical vs. product issues
- Multi-Channel Logic: Can notify multiple channels for critical issues
Each channel has:
- Description: Detailed explanation of what belongs there
- Keywords: Specific terms that indicate relevance
- Priority Level: High/medium/low for urgency-based routing
- Email Address: Delivery target
- GitHub: PR reviews, security alerts, deployment status
- Slack: Direct Slack channel delivery (alternative to email)
- PagerDuty: Incident management integration
- Custom Webhooks: Generic webhook handler for other services
- Smart Scheduling: Respect time zones and on-call schedules
- Escalation Logic: Auto-escalate if no response within timeframe
- Analytics Dashboard: Track notification patterns and effectiveness
- Custom Rules: User-defined routing rules beyond AI
- Digest Mode: Batch low-priority notifications
When adding new integrations:
- Create a new webhook handler file (e.g.,
sentry-webhook.ts
) - Convert the source payload to
NotificationData
format in your handler - Call
processNotification(notificationData)
from the shared triage system - Include GET endpoint for configuration instructions
- Add any source-specific configuration to
CONFIGURATION.ts
- Update this README with new features
The shared triage system expects all notifications in this standardized format:
interface NotificationData {
source: string; // e.g., 'linear', 'sentry', 'billing'
id: string; // unique identifier
type: string; // e.g., 'issue', 'error', 'payment_failed'
action: string; // e.g., 'created', 'updated', 'resolved'
title: string;
description?: string;
priority?: number | string;
severity?: 'low' | 'medium' | 'high' | 'critical';
labels?: string[];
team?: string;
assignee?: { name: string; email?: string };
url?: string;
// ... additional fields
}
The system will automatically:
- Generate citation contexts for better AI prompting
- Create proper links in notifications
- Apply fallback routing logic
- Format emails consistently across all sources
- Webhook Logs: Check Val Town logs for processing details
- Structured Logging: All webhook payloads and AI responses are logged using LLML formatting for easy reading
- AI Transparency: Triage reasoning is logged with clear separators (e.g., "=== AI Triage Result ===")
- Email Delivery: Individual channel delivery status is logged
- Signature Verification: Failed authentications are logged with details
- LLML Formatting: Complex objects are automatically formatted as readable XML-like structures
=== Linear Webhook Payload ===
<LinearWebhookPayload>
<action>create</action>
<type>Issue</type>
<data>
<title>Critical payment bug</title>
<priority>1</priority>
<labels>
<item><name>critical</name></item>
</labels>
</data>
</LinearWebhookPayload>
=== End Linear Webhook Payload ===
=== AI Triage Result ===
<TriageResult>
<selectedChannels>
<item>engineering-critical</item>
</selectedChannels>
<reasoning>High priority payment issue requires immediate engineering attention</reasoning>
</TriageResult>
=== End AI Triage Result ===