# Notifications The notif package provides a notification dispatching system for GoDoxy, supporting multiple providers (Webhook, Gotify, Ntfy) with retry logic and exponential backoff. ## Overview The notif package implements a notification dispatcher that delivers messages to multiple providers with automatic retry handling, provider management, and logging support. ### Key Features - Multiple notification providers (Webhook, Gotify, Ntfy) - Provider registration and management - Retry logic with exponential backoff - Message queuing with configurable buffer - Selective provider targeting - Colored message support ## Architecture ```mermaid graph TD A[LogMessage] --> B[Dispatcher] B --> C{Provider Filter} C -->|Matches| D[Send to Provider] C -->|No Match| E[Skip] D --> F{Send Success?} F -->|Yes| G[Log Success] F -->|No| H[Retry Queue] H --> I{Retry Limit?} I -->|No| J[Schedule Retry] J --> B I -->|Yes| K[Log Failure] subgraph Providers L[Webhook] M[Gotify] N[Ntfy] end D --> L D --> M D --> N ``` ## Core Components ### Dispatcher ```go type Dispatcher struct { task *task.Task providers *xsync.Map[Provider, struct{}] logCh chan *LogMessage retryMsg *xsync.Map[*RetryMessage, struct{}] retryTicker *time.Ticker } ``` ### LogMessage ```go type LogMessage struct { Level zerolog.Level Title string Body LogBody Color Color To []string // Provider names to target } type LogBody []string ``` ### Provider Interface ```go type Provider interface { GetName() string Send(ctx context.Context, msg *LogMessage) error } ``` ## Providers ### Webhook ```go type Webhook struct { URL string `json:"url"` Method string `json:"method"` // default: POST } ``` ### Gotify ```go type GotifyClient struct { URL string `json:"url"` Token string `json:"token"` } ``` ### Ntfy ```go type Ntfy struct { URL string `json:"url"` Topic string `json:"topic"` } ``` ## Public API ### Dispatcher Management ```go // StartNotifDispatcher initializes the notification dispatcher. func StartNotifDispatcher(parent task.Parent) *Dispatcher ``` ### Notification ```go // Notify sends a log message to all providers. func Notify(msg *LogMessage) ``` ### Dispatcher Methods ```go // RegisterProvider registers a notification provider. func (disp *Dispatcher) RegisterProvider(cfg *NotificationConfig) ``` ## Usage ### Basic Notification ```go notif.Notify(¬if.LogMessage{ Level: zerolog.InfoLevel, Title: "Container Started", Body: notif.ListBody{ "Container: myapp", "Status: Running", }, }) ``` ### Provider-Specific Notification ```go notif.Notify(¬if.LogMessage{ Level: zerolog.WarnLevel, Title: "High Memory Usage", Body: notif.MessageBody("Memory: 85%"), Color: notif.ColorRed, To: []string{"gotify"}, // Only send to provider with name "gotify" }) ``` ### Registering Providers ```go dispatcher := notif.StartNotifDispatcher(parent) dispatcher.RegisterProvider(¬if.NotificationConfig{ ProviderName: notif.ProviderWebhook, Provider: ¬if.Webhook{ URL: "https://hooks.example.com/webhook", }, }) dispatcher.RegisterProvider(¬if.NotificationConfig{ ProviderName: notif.ProviderGotify, Provider: ¬if.GotifyClient{ URL: "https://gotify.example.com", Token: "secret", }, }) ``` ## Retry Logic ### Configuration ```go const ( retryInterval = time.Second maxBackoffDelay = 5 * time.Minute backoffMultiplier = 2.0 ) ``` ### Backoff Calculation ```go func calculateBackoffDelay(trials int) time.Duration { if trials == 0 { return retryInterval } delay := min(float64(retryInterval)*math.Pow(backoffMultiplier, float64(trials)), float64(maxBackoffDelay)) // Add 20% jitter jitter := delay * 0.2 * (rand.Float64() - 0.5) return time.Duration(delay + jitter) } ``` ### Retry Schedule | Trial | Delay | Jitter | Total (approx) | | ----- | ------ | --------- | -------------- | | 0 | 1s | +/- 100ms | 0.9-1.1s | | 1 | 2s | +/- 200ms | 1.8-2.2s | | 2 | 4s | +/- 400ms | 3.6-4.4s | | 3 | 8s | +/- 800ms | 7.2-8.8s | | 4 | 16s | +/- 1.6s | 14.4-17.6s | | 5 | 32s | +/- 3.2s | 28.8-35.2s | | ... | max 5m | +/- 30s | 4.5-5.5m | ## Data Flow ```mermaid sequenceDiagram participant App participant Dispatcher participant Queue participant Provider participant RetryScheduler App->>Dispatcher: Notify(LogMessage) Dispatcher->>Dispatcher: Buffer Message Dispatcher->>Dispatcher: Dispatch Async par Parallel Provider Delivery Dispatcher->>Provider: Send() alt Success Provider-->>Dispatcher: nil Dispatcher->>Dispatcher: Log Success else Failure Provider-->>Dispatcher: Error Dispatcher->>RetryScheduler: Schedule Retry end end loop Retry Ticker RetryScheduler->>Dispatcher: Process Retries Dispatcher->>Provider: Retry Send end ``` ## Message Colors ```go type Color uint const ( ColorDefault Color = iota ColorRed ColorGreen ColorBlue ColorYellow ColorPurple ) ``` ## Configuration ### YAML Configuration ```yaml providers: notification: - provider: webhook url: https://hooks.example.com/webhook method: POST - provider: gotify url: https://gotify.example.com token: your-token - provider: ntfy url: https://ntfy.example.com topic: godoxy ``` ## Integration Points The notif package integrates with: - **ACL**: Blocked access notifications - **Route**: Route status changes - **Idlewatcher**: Container idle/alive notifications - **Health**: Health check alerts - **Autocert**: Certificate expiration warnings ## Error Handling ### Provider Errors ```go var ( ErrMissingNotifProvider = errors.New("missing notification provider") ErrInvalidNotifProviderType = errors.New("invalid notification provider type") ErrUnknownNotifProvider = errors.New("unknown notification provider") ) ``` ### Retry Limits Retry limits depend on message level: ```go var maxRetries = map[zerolog.Level]int{ zerolog.ErrorLevel: 3, zerolog.WarnLevel: 2, zerolog.InfoLevel: 1, zerolog.DebugLevel: 0, } ``` ## Performance Considerations - Buffered channel with 100 message capacity - Non-blocking sends to provider - Batched retry processing - Provider filtering reduces unnecessary calls - Exponential backoff prevents thundering herd