Pulse/NOTIFICATION_SYSTEM_MAP.md
rcourtman 9d2bad3af6 Add notification system documentation and fix tab panel corner radius
- Add NOTIFICATION_AUDIT.md for system analysis
- Add NOTIFICATION_QUICK_REFERENCE.md for quick lookup
- Add NOTIFICATION_SYSTEM_MAP.md for architecture overview
- Fix tab panel missing rounded-tl corner when first tab is active
2025-11-07 08:19:50 +00:00

1227 lines
38 KiB
Markdown

# Pulse Notification System Architecture - Complete Map
## Overview
Pulse has a sophisticated multi-channel notification system supporting Email, Webhooks, and Apprise. It features persistent queuing, retry logic, rate limiting, and security controls.
---
## BACKEND IMPLEMENTATION
### 1. Core Notification Manager
**File**: `/opt/pulse/internal/notifications/notifications.go` (2,358 lines)
#### Key Structures:
- **NotificationManager** (lines 107-126)
- Orchestrates email, webhook, and Apprise notifications
- Manages alert grouping with time-based windows
- Implements cooldown and rate limiting
- Maintains webhook delivery history
- Integrates with persistent notification queue
- **EmailConfig** (lines 266-278)
```go
type EmailConfig struct {
Enabled bool
Provider string // Gmail, SendGrid, etc.
SMTPHost string // "server" in JSON
SMTPPort int // "port" in JSON
Username string
Password string
From string
To []string
TLS bool
StartTLS bool // STARTTLS support
}
```
- **WebhookConfig** (lines 280-291)
```go
type WebhookConfig struct {
ID string
Name string
URL string
Method string
Headers map[string]string
Enabled bool
Service string // discord, slack, teams, etc.
Template string // Custom payload template
CustomFields map[string]string
}
```
- **AppriseConfig** (lines 301-313)
```go
type AppriseConfig struct {
Enabled bool
Mode AppriseMode // "cli" or "http"
Targets []string
CLIPath string
TimeoutSeconds int
ServerURL string
ConfigKey string
APIKey string
APIKeyHeader string
SkipTLSVerify bool
}
```
#### Key Methods:
**Initialization & Configuration**:
- `NewNotificationManager(publicURL string)` (lines 315-361)
- Creates persistent queue if available
- Initializes alert grouping system
- Wires up queue processor
- `SetEmailConfig(config)` (lines 388-406)
- Creates new email manager with provider config
- Updates configuration in-memory
- `SetAppriseConfig(config)` (lines 408-413)
- Normalizes Apprise configuration
- Validates mode and timeout settings
- `GetEmailConfig() / GetWebhooks() / GetAppriseConfig()` (lines 502-420)
- Safe accessor methods with locking
**Webhook Management**:
- `AddWebhook(webhook)` (lines 453-458)
- `UpdateWebhook(id, webhook)` (lines 460-472)
- `DeleteWebhook(id)` (lines 474-486)
- `GetWebhooks()` (lines 488-500)
**Alert Sending**:
- `SendAlert(alert)` (lines 516-569)
- Implements cooldown check
- Adds alert to pending alerts queue
- Starts alert grouping timer
- Checks for rate limits
- `CancelAlert(alertID)` (lines 571-614)
- Removes resolved alert from pending list
- Stops grouping timer if no more pending alerts
- `sendGroupedAlerts()` (lines 616-657)
- Sends all pending alerts as single batch
- Updates last notified timestamps
- Enqueues to persistent queue OR sends directly
**Email Sending**:
- `sendGroupedEmail(config, alertList)` (lines 753-764)
- Uses email template
- Calls enhanced email manager with retry
- `sendHTMLEmailWithError(subject, body, config)` (lines 1010-1076)
- Handles HTML + text multipart emails
- Returns error for caller
- Uses shared email manager for rate limiting
- `sendHTMLEmail(subject, body, config)` (lines 1078-1133)
- Creates EnhancedEmailManager instance
- Sends with retries (2 attempts, 3 second delay)
**Webhook Sending**:
- `sendGroupedWebhook(webhook, alertList)` (lines 1141-1354)
- Handles single/grouped alert scenarios
- Applies service-specific templates (Discord, Slack, Teams, etc.)
- Supports custom templates
- Service-specific data enrichment (Telegram chat_id, PagerDuty routing key)
- `sendWebhookRequest(webhook, jsonData, alertType)` (lines 1396-1533)
- Performs rate limit check
- Creates HTTP request with headers
- Sends using secure webhook client
- Tracks delivery history
- `prepareWebhookData(alert, customFields)` (lines 1708-1771)
- Builds WebhookPayloadData struct
- Formats metrics and duration
- Includes metadata and custom fields
**Apprise Integration**:
- `sendGroupedApprise(config, alertList)` (lines 766-803)
- Routes to CLI or HTTP mode
- Validates configuration before sending
- `sendAppriseViaCLI(config, title, body)` (lines 866-902)
- Executes apprise CLI with timeout
- Passes targets as arguments
- `sendAppriseViaHTTP(config, title, body, type)` (lines 904-992)
- Makes HTTP POST to Apprise server
- Supports custom endpoints with configKey
- Handles API key header
- Optional TLS verification skip
**Webhook Validation & Security**:
- `ValidateWebhookURL(webhookURL)` (lines 1921-1988)
- Prevents SSRF attacks
- Blocks localhost/loopback (127.0.0.1, ::1)
- Blocks link-local addresses (169.254.*, fe80::)
- Blocks private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Blocks cloud metadata services (169.254.169.254, metadata.google.internal)
- DNS resolution required for security
- Warns on numeric IPs with HTTPS
- `isPrivateIP(ip)` (lines 1990-2014)
- Checks IPv4 and IPv6 private ranges
**Webhook URL Templating**:
- `renderWebhookURL(urlTemplate, data)` (lines 1829-1865)
- Parses Go template syntax
- Validates rendered URL is valid
- Supports {{.}} field references
**Template Support**:
- `generatePayloadFromTemplate(templateStr, data)` (lines 1793-1796)
- `generatePayloadFromTemplateWithService(templateStr, data, service)` (lines 1798-1827)
- Parses and executes Go text templates
- Custom function map: title, upper, lower, printf, urlquery, urlencode, urlpath, pathescape
- JSON validation (except ntfy service)
**Telegram-specific**:
- `extractTelegramChatID(webhookURL)` (lines 1882-1909)
- Extracts and validates chat_id parameter
- Handles negative IDs (group chats)
- Validates numeric format
**Test Notifications**:
- `SendTestNotification(method)` (lines 2117-2195)
- Creates test alert with predefined values
- Routes to email/webhook/apprise
- `SendTestWebhook(webhook)` (lines 2197-2229)
- Tests specific webhook with sample alert
- `SendTestNotificationWithConfig(method, config, nodeInfo)` (lines 2231-2300)
- Tests email with provided config (without saving)
- Uses actual node info from monitor state
**Queue Integration**:
- `enqueueNotifications(emailConfig, webhooks, appriseConfig, alerts)` (lines 659-722)
- Marshals configs to JSON
- Creates QueuedNotification structs
- Adds to persistent queue
- `ProcessQueuedNotification(notif)` (lines 2302-2335)
- Unmarshals config based on type
- Routes to appropriate sender
- Called by queue processor
**Utility Functions**:
- `checkWebhookRateLimit(webhookURL)` (lines 1356-1394)
- Per-webhook rate limiting (10 per minute)
- Tracks sent count per window
- `formatWebhookDuration(duration)` (lines 1867-1880)
- Human-readable duration: "5s", "2m", "1h 30m", "2d 3h"
- `NormalizeAppriseConfig(config)` (lines 176-240)
- Cleans whitespace
- Validates mode and server URL
- Removes duplicate targets
- Enforces timeout bounds (5-120 seconds)
---
### 2. Enhanced Webhook Support
**File**: `/opt/pulse/internal/notifications/webhook_enhanced.go` (605 lines)
#### Key Structures:
- **EnhancedWebhookConfig** (lines 17-27)
```go
type EnhancedWebhookConfig struct {
WebhookConfig
Service string
PayloadTemplate string
RetryEnabled bool
RetryCount int
FilterRules WebhookFilterRules
CustomFields map[string]interface{}
ResponseLogging bool
}
```
- **WebhookFilterRules** (lines 29-35)
- Filter by alert level, type, node, resource type
- **WebhookPayloadData** (lines 37-66)
- Complete template data structure
- Includes alerts array for grouped notifications
- ChatID for Telegram webhooks
#### Key Methods:
- `SendEnhancedWebhook(webhook, alert)` (lines 68-124)
- `shouldSendWebhook(webhook, alert)` (lines 129-194)
- `sendWebhookWithRetry(webhook, payload)` (lines 196-326)
- Exponential backoff: 1s → 2s → 4s → 8s → 16s → 30s (max)
- Respects Retry-After header for 429 responses
- Max 3 retries by default
- Logs attempt counts
- `isRetryableWebhookError(err)` (lines 328-365)
- Network errors: timeout, connection refused/reset, no such host
- Retryable HTTP: 429, 502, 503, 504, 5xx
- Non-retryable: 4xx client errors
- `sendWebhookOnceWithResponse(webhook, payload)` (lines 367-428)
- Returns response for inspection
- Size-limited response reading
- `sendWebhookOnce(webhook, payload)` (lines 430-434)
- Wrapper for compatibility
- `TestEnhancedWebhook(webhook)` (lines 438-604)
- Tests webhook with realistic sample alert
- Handles Telegram chat_id extraction
- PagerDuty routing key injection
- ntfy service special header handling
- Returns (statusCode, responseBody, error)
---
### 3. Email Implementation
**File**: `/opt/pulse/internal/notifications/email_enhanced.go` (415 lines)
#### Key Structures:
- **EnhancedEmailManager** (lines 15-19)
```go
type EnhancedEmailManager struct {
config EmailProviderConfig
rateLimit *RateLimiter
}
```
- **RateLimiter** (lines 21-26)
- Simple per-minute rate limiting
#### Key Methods:
- `NewEnhancedEmailManager(config)` (lines 28-36)
- `SendEmailWithRetry(subject, htmlBody, textBody)` (lines 38-78)
- Up to 3 retry attempts (configurable)
- 5 second delay between retries (configurable)
- Checks rate limit before each attempt
- `checkRateLimit()` (lines 80-99)
- Per-minute limiter
- Default 60 emails/minute
- `sendEmailOnce(subject, htmlBody, textBody)` (lines 101-138)
- Builds multipart MIME message
- Sets standard headers (Date, Message-ID, MIME-Version)
- Creates text + HTML parts
- Calls provider-specific send
- `sendViaProvider(msg)` (lines 140-183)
- Provider-specific username/password handling
- SendGrid: "apikey" as username
- Postmark: API token for both
- SparkPost: "SMTP_Injection" username
- Resend: "resend" username
- `sendTLS(addr, auth, msg)` (lines 185-245)
- TLS from start (port 465)
- Uses tls.DialWithDialer with timeout
- Sets connection deadline (30s)
- `sendStartTLS(addr, auth, msg)` (lines 247-309)
- Plain TCP then STARTTLS upgrade
- 10s dial timeout, 30s connection deadline
- `sendPlain(addr, auth, msg)` (lines 362-414)
- Plain SMTP without encryption
- 10s dial timeout, 30s connection deadline
- `TestConnection()` (lines 311-360)
- Tests SMTP connectivity
- Supports TLS and STARTTLS modes
- Tests authentication if configured
---
### 4. Email Configuration & Templates
**Files**:
- `/opt/pulse/internal/notifications/email_providers.go` (217 lines)
- `/opt/pulse/internal/notifications/email_template.go` (200+ lines)
#### Email Providers (email_providers.go):
- **GetEmailProviders()** returns templates for:
1. Gmail / Google Workspace (STARTTLS, port 587)
2. SendGrid (STARTTLS, port 587)
3. Mailgun (STARTTLS, port 587)
4. Amazon SES (STARTTLS, port 587)
5. Microsoft 365 / Outlook (STARTTLS, port 587, App Password required)
6. Brevo / Sendinblue (STARTTLS, port 587)
7. Postmark (STARTTLS, port 587)
8. SparkPost (STARTTLS, port 587)
9. Resend (STARTTLS, port 587)
10. SMTP2GO (STARTTLS, port 587)
11. Custom SMTP Server
- Each provider includes:
- SMTP host and port
- TLS/STARTTLS settings
- Authentication requirements
- Setup instructions with links
#### Email Templates (email_template.go):
- **EmailTemplate(alertList, isSingle)** (lines 11-17)
- Routes to single or grouped template
- **singleAlertTemplate(alert)** (lines 19-40+)
- Professional HTML email with:
- Responsive design
- Level-specific colors (red for critical, yellow for warning)
- Alert metrics display
- Details section with duration, node, type
- Links to Pulse dashboard
- Footer with Pulse logo and branding
- **groupedAlertTemplate(alertList)** (lines 100+)
- Multiple alert summary
- All alerts listed with values
- Single cohesive email
---
### 5. Webhook Templates
**File**: `/opt/pulse/internal/notifications/webhook_templates.go` (300+ lines)
#### GetWebhookTemplates() provides for:
1. **Discord Webhook**
- Uses embed format with color coding
- Fields: Resource, Node, Type, Value, Threshold, Duration
- Includes timestamp and footer
2. **Telegram Bot**
- Markdown formatted message
- Inline link to Pulse dashboard
- Requires chat_id parameter
3. **Slack Incoming Webhook**
- Header block with alert level
- Section with message
- 6-field layout (Resource, Node, Type, Value, Threshold, Duration)
- Links to Proxmox and Pulse
4. **Microsoft Teams**
- Adaptive Card format
- Facts section with details
- Color-coded by alert level
5. **PagerDuty**
- Event API format
- Routing key from headers
6. **Gotify**
- Title + message format
- Priority based on level
7. **Pushover**
- Custom field aliases (app_token, user_token)
- Priority encoding
8. **ntfy.sh**
- Plain text content
- Title and Priority headers
- Tags header with emoji/type
Each template includes:
- Go template syntax for field substitution
- Service-specific format requirements
- Instructions for setup
---
### 6. Persistent Notification Queue
**File**: `/opt/pulse/internal/notifications/queue.go` (600+ lines)
#### Key Structures:
- **QueuedNotification** (lines 29-45)
```go
type QueuedNotification struct {
ID string
Type string // email, webhook, apprise
Method string
Status NotificationQueueStatus // pending, sending, sent, failed, dlq, cancelled
Alerts []*alerts.Alert
Config json.RawMessage
Attempts int
MaxAttempts int
LastAttempt *time.Time
LastError string
CreatedAt time.Time
NextRetryAt *time.Time
CompletedAt *time.Time
PayloadBytes int
}
```
- **NotificationQueue** (lines 47-58)
- SQLite-backed persistent queue
- Background processors:
- `processQueue()`: Periodic retry processor (5s interval)
- `cleanupOldEntries()`: Cleanup job (1 hour interval)
- Notification channel for signaling new items
#### Database Schema (lines 115-150):
- **notification_queue** table
- Primary key: id (TEXT)
- Statuses: pending, sending, sent, failed, dlq, cancelled
- Indexes: status, next_retry_at (for pending), created_at
- WAL mode for concurrency
- NORMAL synchronous for durability
- **notification_audit** table (lines 139-150)
- Audit trail for all notifications
- Alert IDs and count tracking
- Success/failure metrics
#### Key Methods:
- `NewNotificationQueue(dataDir)` (lines 60-113)
- Opens/creates SQLite database
- Configures pragmas:
- WAL mode for better concurrency
- NORMAL synchronous for balance
- 5s busy timeout
- 64MB cache
- Initializes schema
- Starts background processors
- `Enqueue(notif)` (enqueues notifications)
- `GetQueueStats()` (returns queue statistics)
- `GetDLQ(limit)` (retrieves dead letter queue)
- `RetryDLQItem(id)` (retries failed notification)
- `DeleteDLQItem(id)` (removes from DLQ)
- `SetProcessor(func)` (sets notification processor)
---
## API LAYER
### 1. Notification Endpoints Handler
**File**: `/opt/pulse/internal/api/notifications.go` (753 lines)
#### NotificationHandlers Structure (lines 17-27):
- Reference to Monitor for accessing notification manager and config persistence
#### GET Endpoints:
| Path | Method | Handler | Auth Scope | Purpose |
|------|--------|---------|-----------|---------|
| `/api/notifications/email` | GET | GetEmailConfig | SettingsRead | Retrieve current email config (password masked) |
| `/api/notifications/webhooks` | GET | GetWebhooks | SettingsRead | List all webhooks (sensitive fields masked) |
| `/api/notifications/webhook-templates` | GET | GetWebhookTemplates | SettingsRead | Get available webhook templates |
| `/api/notifications/webhook-history` | GET | GetWebhookHistory | SettingsRead | Get recent webhook delivery history |
| `/api/notifications/email-providers` | GET | GetEmailProviders | SettingsRead | Get email provider templates |
| `/api/notifications/apprise` | GET | GetAppriseConfig | SettingsRead | Get Apprise configuration |
| `/api/notifications/health` | GET | GetNotificationHealth | SettingsRead | Get queue and channel health stats |
#### PUT/UPDATE Endpoints:
| Path | Method | Handler | Auth Scope | Purpose |
|------|--------|---------|-----------|---------|
| `/api/notifications/email` | PUT | UpdateEmailConfig | SettingsWrite | Update email config (preserves password) |
| `/api/notifications/apprise` | PUT | UpdateAppriseConfig | SettingsWrite | Update Apprise config |
| `/api/notifications/webhooks/{id}` | PUT | UpdateWebhook | SettingsWrite | Update specific webhook |
#### POST Endpoints:
| Path | Method | Handler | Auth Scope | Purpose |
|------|--------|---------|-----------|---------|
| `/api/notifications/webhooks` | POST | CreateWebhook | SettingsWrite | Create new webhook |
| `/api/notifications/webhooks/test` | POST | TestWebhook | SettingsWrite | Test webhook with sample alert |
| `/api/notifications/test` | POST | TestNotification | SettingsWrite | Test email/webhook/apprise |
#### DELETE Endpoints:
| Path | Method | Handler | Auth Scope | Purpose |
|------|--------|---------|-----------|---------|
| `/api/notifications/webhooks/{id}` | DELETE | DeleteWebhook | SettingsWrite | Delete webhook |
#### Key Handler Methods:
**GetEmailConfig (lines 34-43)**:
- Returns masked config (password redacted)
- JSON response
**UpdateEmailConfig (lines 45-89)**:
- Reads raw body
- Preserves existing password if new one empty
- Updates in-memory config
- Persists to storage via ConfigPersistence
**GetWebhooks (lines 142-186)**:
- Masks header and customField values
- Shows only keys, not secrets
- Preserves template if present
**CreateWebhook (lines 188-233)**:
- Generates ID if not provided
- Validates URL (SSRF protection)
- Saves all webhooks to persistent storage
- Returns full webhook data
**UpdateWebhook (lines 235-289)**:
- Validates URL before update
- Updates webhook in manager
- Persists all webhooks to storage
- Returns updated webhook
**DeleteWebhook (lines 291-317)**:
- Removes webhook from manager
- Persists remaining webhooks
**TestNotification (lines 319-421)**:
- Supports "email" or "webhook" method
- Accepts optional config for testing without saving
- Gets actual node info from monitor state
- Routes to appropriate test sender
**TestWebhook (lines 487-601)**:
- Decodes webhook configuration
- Applies service-specific templates
- Calls enhanced webhook test
- Returns (statusCode, response body)
**GetNotificationHealth (lines 603-648)**:
- Returns queue statistics:
- pending, sending, sent, failed, dlq counts
- Healthy flag
- Email status: enabled, configured
- Webhook status: total, enabled count
- Overall health status
**GetWebhookHistory (lines 431-444)**:
- Returns recent webhook deliveries (last 100)
- Redacts secrets from URLs
- Redacts Telegram bot tokens
- Redacts query parameter secrets (token, apikey, key, secret, password)
**redactSecretsFromURL (lines 446-477)**:
- Telegram: /botXXXX:REDACTED/
- Query params: token=REDACTED, apikey=REDACTED, etc.
#### Security Features:
- All handlers require authentication (RequireAdmin wrapper)
- Sensitive fields masked/redacted
- SSRF protection on webhook URLs
- Password preservation on updates
- Scope-based access control (SettingsRead/SettingsWrite)
---
### 2. Notification Queue Endpoints Handler
**File**: `/opt/pulse/internal/api/notification_queue.go` (80+ lines)
#### NotificationQueueHandlers Structure:
- Reference to Monitor
#### Endpoints:
| Path | Method | Handler | Auth Scope |
|------|--------|---------|-----------|
| `/api/notifications/dlq` | GET | GetDLQ | MonitoringRead |
| `/api/notifications/queue/stats` | GET | GetQueueStats | MonitoringRead |
| `/api/notifications/dlq/retry` | POST | RetryDLQItem | MonitoringRead |
| `/api/notifications/dlq/delete` | POST | DeleteDLQItem | MonitoringRead |
#### Key Methods:
- **GetDLQ (lines 27-56)**:
- Returns dead letter queue items
- Supports limit parameter (default 100, max 1000)
- Returns JSON array
- **GetQueueStats (lines 58-80)**:
- Returns dictionary of queue statistics
- Pending, sending, sent, failed, dlq counts
- Healthy flag
---
### 3. Configuration Persistence
Integration with ConfigPersistence (mentioned in notifications.go):
- `SaveEmailConfig(config)` - Persists email settings
- `SaveWebhooks(webhooks)` - Persists all webhook configurations
- `SaveAppriseConfig(config)` - Persists Apprise settings
These are called after every modification to ensure durability.
---
### 4. Router Integration
**File**: `/opt/pulse/internal/api/router.go`
#### Notification Handler Registration (lines 147-148, 900-926):
```go
r.notificationHandlers = NewNotificationHandlers(r.monitor)
r.notificationQueueHandlers = NewNotificationQueueHandlers(r.monitor)
// Main notification endpoints
r.mux.HandleFunc("/api/notifications/", RequireAdmin(r.config, r.notificationHandlers.HandleNotifications))
// Queue management endpoints
r.mux.HandleFunc("/api/notifications/dlq", RequireAdmin(r.config, r.notificationQueueHandlers.GetDLQ))
r.mux.HandleFunc("/api/notifications/queue/stats", RequireAdmin(r.config, r.notificationQueueHandlers.GetQueueStats))
r.mux.HandleFunc("/api/notifications/dlq/retry", RequireAdmin(r.config, r.notificationQueueHandlers.RetryDLQItem))
r.mux.HandleFunc("/api/notifications/dlq/delete", RequireAdmin(r.config, r.notificationQueueHandlers.DeleteDLQItem))
```
#### Monitor Reference Update (line 1125-1126):
- Notification handlers updated when Monitor reference changes
- Ensures handlers always use latest monitor instance
#### Public URL Detection (line 1516):
- Public URL detected from inbound requests for webhook payload generation
---
## FRONTEND IMPLEMENTATION
### 1. Notification API Client
**File**: `/opt/pulse/frontend-modern/src/api/notifications.ts` (201 lines)
#### Interface Types:
**EmailConfig**:
```typescript
interface EmailConfig {
enabled: boolean;
provider: string;
server: string;
port: number;
username: string;
password?: string;
from: string;
to: string[];
tls: boolean;
startTLS: boolean;
}
```
**Webhook**:
```typescript
interface Webhook {
id: string;
name: string;
url: string;
method: string;
headers: Record<string, string>;
template?: string;
enabled: boolean;
service?: string;
customFields?: Record<string, string>;
}
```
**AppriseConfig**:
```typescript
interface AppriseConfig {
enabled: boolean;
mode?: 'cli' | 'http';
targets?: string[];
cliPath?: string;
timeoutSeconds?: number;
serverUrl?: string;
configKey?: string;
apiKey?: string;
apiKeyHeader?: string;
skipTlsVerify?: boolean;
}
```
#### API Methods (NotificationsAPI class):
**Email Configuration**:
- `getEmailConfig()` → EmailConfig
- Returns config with password field
- `updateEmailConfig(config)` → { success: boolean }
- Sends: server, port, tls, startTLS (not smtpHost/smtpPort)
- Sends: enabled, provider, username, password, from, to
**Webhook Management**:
- `getWebhooks()` → Webhook[]
- `createWebhook(webhook)` → Webhook
- `updateWebhook(id, webhook)` → Webhook
- `deleteWebhook(id)` → { success: boolean }
**Apprise Configuration**:
- `getAppriseConfig()` → AppriseConfig
- `updateAppriseConfig(config)` → AppriseConfig
**Templates & Providers**:
- `getEmailProviders()` → EmailProvider[]
- `getWebhookTemplates()` → WebhookTemplate[]
**Testing**:
- `testNotification(request)` → { success: boolean; message?: string }
- Request: { type: 'email'|'webhook', config?: object, webhookId?: string }
- `testWebhook(webhook)` → { success: boolean; message?: string }
---
### 2. Frontend Components
#### Email Provider Selector
**File**: `/opt/pulse/frontend-modern/src/components/Alerts/EmailProviderSelect.tsx` (200+ lines)
Features:
- Loads provider templates dynamically
- Dropdown select with provider options
- Auto-fills SMTP host, port, TLS/STARTTLS based on selection
- Displays setup instructions for selected provider
- Advanced settings toggle (Reply-To, Max Retries, Rate Limit)
- Test connection button
Key States:
- `providers` - Loaded email provider templates
- `showAdvanced` - Toggle advanced settings display
- `showInstructions` - Show provider-specific setup instructions
Key Functions:
- `applyProvider(provider)` - Updates form with provider defaults
- `handleProviderChange(value)` - Routes provider selection
- `currentProvider()` - Gets currently selected provider
#### Webhook Configuration
**File**: `/opt/pulse/frontend-modern/src/components/Alerts/WebhookConfig.tsx` (300+ lines)
Features:
- Create/update/delete webhooks
- Service-specific configuration (Discord, Slack, Teams, Telegram, etc.)
- Custom headers management
- Custom fields management (e.g., Pushover app_token, user_token)
- Template selection from provided templates
- Custom payload template support
- Test webhook functionality
- URL template rendering support
Key States:
- `webhooks` - List of configured webhooks
- `editingId` - Currently editing webhook ID
- `selectedService` - Selected service type
- `customHeaders` - Header key-value inputs
- `customFields` - Custom field inputs based on service
- `templates` - Available webhook templates
Key Features:
- Service-specific custom field presets (Pushover fields, etc.)
- Template preview with field substitution
- URL template support with {{.}} placeholders
- Payload validation
- Header value masking (shows "***REDACTED**" for sensitive values)
#### Notification Stores
**File**: `/opt/pulse/frontend-modern/src/stores/notifications.ts`
Reactive state management for:
- Email configuration
- Webhook list
- Apprise configuration
- UI state (loading, errors, testing)
#### Settings Integration
**File**: `/opt/pulse/frontend-modern/src/components/Settings/Settings.tsx`
Integration point for:
- Email notification settings page
- Webhook management interface
- Apprise configuration
- Notification health monitoring
---
## DATA FLOW & SEQUENCES
### Alert Notification Flow
```
Alert Triggered (in monitoring)
monitor.GetNotificationManager().SendAlert(alert)
CheckCooldown → Skip if in cooldown
AddToPendingAlerts → Start GroupingTimer
GroupingWindow Timer Expires (default 30s)
sendGroupedAlerts()
├→ Snapshot configurations (email, webhooks, apprise)
├→ If PersistentQueue available:
│ └→ enqueueNotifications() → Queue.Enqueue()
└→ If No Queue:
├→ sendNotificationsDirect()
├→ For each enabled webhook:
│ └→ sendGroupedWebhook() → async goroutine
├→ If email enabled:
│ └→ sendGroupedEmail() → async goroutine
└→ If apprise enabled:
└→ sendGroupedApprise() → async goroutine
```
### Webhook Sending Flow
```
sendGroupedWebhook(webhook, alertList)
Check for custom template
├→ If custom template exists:
│ └→ generatePayloadFromTemplateWithService()
└→ If service-specific (Discord, Slack, etc.):
├→ Lookup service template
├→ Apply service-specific data enrichment
├→ generatePayloadFromTemplateWithService()
└→ If no template found:
└→ Use generic JSON payload
sendWebhookRequest(webhook, payload)
├→ checkWebhookRateLimit()
├→ Create HTTP request
├→ Set headers
├→ Send with secure client (SSRF protection)
├→ Read response (max 1MB)
└→ addWebhookDelivery() to history
```
### Email Sending Flow
```
sendGroupedEmail(config, alertList)
EmailTemplate(alertList, isSingle)
├→ Grouped vs single template
└→ Generate HTML + text body
sendHTMLEmailWithError(subject, htmlBody, textBody, config)
├→ Use From as To if To empty
├→ Get/create EnhancedEmailManager
└→ SendEmailWithRetry()
├→ Attempt 1: sendEmailOnce()
├→ Check rate limit
├→ Build MIME message
├→ sendViaProvider()
│ ├→ If TLS: sendTLS() [port 465]
│ ├→ If STARTTLS: sendStartTLS() [port 587]
│ └→ Else: sendPlain()
└→ Retry up to 3 times with 5s delays
```
### Persistent Queue Flow
```
NotificationQueue starts (2 background goroutines)
├→ processQueue() - runs every 5 seconds
│ ├→ SELECT WHERE status='pending' AND next_retry_at <= now
│ ├→ For each notification:
│ │ ├→ Update status to 'sending'
│ │ ├→ Call processor.ProcessQueuedNotification()
│ │ ├→ If success → Update status to 'sent', set completed_at
│ │ └→ If failed → Update status to 'failed', set next_retry_at
│ └→ After max attempts → Move to 'dlq' status
└→ cleanupOldEntries() - runs hourly
└→ Delete entries older than retention period
```
---
## KEY SECURITY FEATURES
### 1. SSRF Prevention
- DNS resolution required for all webhook URLs
- Blocks localhost: 127.0.0.1, ::1, "localhost", 127.*
- Blocks private ranges: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
- Blocks link-local: 169.254.*, fe80::*
- Blocks cloud metadata: 169.254.169.254, metadata.google.internal, metadata.goog
- Redirect validation: max 3 redirects allowed
- Numeric IP warning for HTTPS connections
### 2. Webhook Security
- Secure client with redirect limits
- Response size limit (1MB max)
- Timeout: 30s default, 10s for tests
- Rate limiting: 10 per minute per webhook URL
- URL validation before creation/update
### 3. Email Security
- Password not logged or returned in responses
- Password preserved on config update if empty
- TLS/STARTTLS enforcement
- Optional TLS verification skip (for self-signed certs)
- SMTP authentication with proper auth methods
### 4. API Security
- Scope-based access control (SettingsRead/SettingsWrite)
- Admin requirement on all notification endpoints
- Sensitive field masking in API responses
- Token/secret redaction in webhook history
- Raw body reading (not automatic JSON decode) for sensitive endpoints
### 5. Queue Security
- Persistent database in user data directory
- SQLite WAL mode for atomicity
- Foreign key constraints
- Audit trail table for all notifications
---
## CONFIGURATION PERSISTENCE
### Storage Mechanism
Configurations are persisted via ConfigPersistence interface:
- Implementations handle storage (likely YAML/JSON files or database)
- Called after every modification
- Survive service restarts
### Persisted Configs
1. Email configuration (EmailConfig struct)
2. All webhook configurations ([]WebhookConfig)
3. Apprise configuration (AppriseConfig struct)
4. Alert notification settings (cooldown, grouping, etc.)
### Startup Sequence
1. Load configs from persistent storage
2. Initialize NotificationManager with loaded configs
3. Initialize EnhancedEmailManager with email config
4. Initialize persistent queue (if enabled)
5. Start background processors
---
## NOTIFICATION TYPES & TRIGGERS
### Alert Levels
- **Critical** (red, #ff6b6b)
- **Warning** (yellow, #ffd93d)
- **Info** (green)
### Alert Types
- cpu
- memory
- disk
- io
- diskRead
- diskWrite
- temperature (from sensor proxy)
### Metrics Formatting
- CPU/Memory: percentages (e.g., 95.5%)
- Disk I/O: MB/s (e.g., 150.2 MB/s)
- Temperature: degrees (e.g., 78.5°C)
### Grouping Configuration
- Cooldown: Per-alert grace period (default 5 minutes)
- Grouping window: Alert batching window (default 30 seconds)
- Group by node: Organize alerts by node
- Group by guest type: Organize alerts by VM/LXC/host
---
## ERROR HANDLING & RETRY LOGIC
### Email Retries
- Max 3 attempts (configurable)
- 5 second delay between attempts (configurable)
- Rate limiting: 60 per minute (configurable)
### Webhook Retries
- Max 3 attempts (configurable per webhook)
- Exponential backoff: 1s, 2s, 4s, 8s, 16s, 30s (max)
- Respects Retry-After header for 429 responses
- Retryable errors: timeouts, network errors, 5xx, 429
- Non-retryable: 4xx client errors (except 429)
### Queue Retries
- Configurable max attempts per notification
- Periodic retry processor (5 second intervals)
- Dead letter queue for permanently failed items
- Manual DLQ retry/delete via API
### Logging
- Structured logging with zerolog
- All critical operations logged with context:
- Alert ID, resource name, type
- Webhook name, service, URL (redacted)
- Email recipients and SMTP server
- Retry counts and errors
---
## CONSTANTS & LIMITS
### Webhook Settings
- Timeout: 30 seconds
- Test timeout: 10 seconds
- Max response size: 1 MB
- Max redirects: 3
- Rate limit: 10 per minute per webhook URL
- Initial backoff: 1 second
- Max backoff: 30 seconds
- Default retries: 3
- History size: Last 100 deliveries
### Email Settings
- Dial timeout: 10 seconds
- Connection deadline: 30 seconds
- Default rate limit: 60 per minute
- Default retries: 3
- Default retry delay: 5 seconds
### Queue Settings
- Processor interval: 5 seconds
- Cleanup interval: 1 hour
- Database journal: WAL mode
- Cache: 64 MB
- Busy timeout: 5 seconds
### Apprise Settings
- Timeout bounds: 5-120 seconds (default 15)
- Max targets: Unlimited (but must have >0)
- CLI path default: "apprise"
- API key header default: "X-API-KEY"
---
## FILES SUMMARY TABLE
| File | Lines | Purpose |
|------|-------|---------|
| `notifications.go` | 2,358 | Core notification manager, alert grouping, webhook/email/apprise sending |
| `webhook_enhanced.go` | 605 | Enhanced webhook support, retry logic, filtering |
| `email_enhanced.go` | 415 | Email manager, SMTP connections, provider support |
| `email_providers.go` | 217 | Email provider templates and instructions |
| `email_template.go` | 200+ | HTML email templates for single/grouped alerts |
| `webhook_templates.go` | 300+ | Templates for Discord, Slack, Teams, Telegram, etc. |
| `queue.go` | 600+ | Persistent notification queue, SQLite backend |
| `notifications.go` (API) | 753 | HTTP handlers for all notification endpoints |
| `notification_queue.go` (API) | 80+ | HTTP handlers for queue management |
| `notifications.ts` (Frontend) | 201 | TypeScript API client for notification endpoints |
| `EmailProviderSelect.tsx` | 200+ | Email provider configuration UI component |
| `WebhookConfig.tsx` | 300+ | Webhook configuration UI component |
---
## INTEGRATION POINTS WITH OTHER SYSTEMS
### Monitor Integration
- `monitor.GetNotificationManager()` - Access notification manager
- `monitor.GetConfigPersistence()` - Persist configurations
- `monitor.GetState()` - Get node and instance info for test notifications
### Alert System Integration
- Receives Alert objects with:
- ID, Type, Level, ResourceName, ResourceID
- Node, Instance, Message
- Value, Threshold, StartTime, LastSeen
- Metadata (resourceType, etc.)
- Acknowledged, AckTime, AckUser
### Router Integration
- `/api/notifications/` main handler routes to sub-endpoints
- All endpoints require admin auth + scope
- Integrated in main API router initialization
### Config System
- Notifications loaded/saved via ConfigPersistence
- Can survive service restarts
- Loaded at startup into NotificationManager
---
## WORKFLOW EXAMPLES
### Example 1: Configure Gmail Notifications
```
User → Settings → Email Notifications
Select "Gmail / Google Workspace" from provider dropdown
Auto-filled:
- Server: smtp.gmail.com
- Port: 587
- STARTTLS: true
- Display: Setup instructions
User enters:
- From: user@gmail.com
- To: [alerts@company.com]
- Username: user@gmail.com
- Password: <app-password>
PUT /api/notifications/email
Backend: UpdateEmailConfig() → SetEmailConfig() → SaveEmailConfig()
Test → POST /api/notifications/test {method: "email"}
SendTestNotification() → sendEmail() → EnhancedEmailManager.SendEmailWithRetry()
SMTP → gmail via STARTTLS → Success/Error logged
Frontend: Display success/error
```
### Example 2: Add Discord Webhook
```
User → Settings → Webhooks → Add New
Select Service: Discord
Copy webhook URL from Discord server
Enter URL: https://discord.com/api/webhooks/...
Backend: ValidateWebhookURL() → DNS check → Private IP check → SSRF check
POST /api/notifications/webhooks
Backend: GenerateID() → AddWebhook() → SaveWebhooks()
Test: POST /api/notifications/webhooks/test
Backend: sendWebhook() → Apply Discord template
Generate payload with embed format → Send HTTP POST
Discord returns 204 → Success logged to webhook history
Frontend: Display success + sample embed preview
```
### Example 3: Alert Triggering Grouped Notifications
```
Monitoring Detects: CPU > 95% on node1
Create Alert → monitor.GetNotificationManager().SendAlert(alert)
NotificationManager.SendAlert():
- Check cooldown → Not in cooldown
- Add to pendingAlerts list
- Start groupingTimer (30s)
Wait 5s → Another alert: Memory > 90% on node1
- Add to pendingAlerts list
- Timer still running
Wait 25s → Grouping timer expires
sendGroupedAlerts() with 2 alerts:
- Snapshot config (2 webhooks, email enabled)
- If queue: Enqueue 3 notifications (email + 2 webhooks)
- If no queue: Send directly (3 async goroutines)
For each webhook:
- sendGroupedWebhook(webhook, [alert1, alert2])
- Prepare template data with AlertCount=2
- Generate combined message
- Send single HTTP POST
For email:
- sendGroupedEmail(emailConfig, [alert1, alert2])
- Template generates grouped email
- Send via SMTP with retries
Queue (if enabled):
- Background processor retries every 5s
- Updates status: pending → sending → sent/failed
- Failed items retry with backoff
- After 3 attempts, move to DLQ
Update last notified timestamps
```