Implement Notification Handlers for Webhook, SMTP, Slack, and Discord
- Dominant language
- Python
- Stars
- 670
- Forks
- 183
- Avg merge
- 17h 7m
- Merged PRs (30d)
- 358
Description
## User Story
As a Backend.AI developer, I want notification handlers for various delivery channels (Webhook, SMTP, Slack, Discord), so that notifications can be delivered to external systems and communication platforms.
## Context
This story implements concrete notification handlers that process NotificationEvent and deliver them through different channels based on NotificationChannel configuration.
## Functional Requirements
### 1. Notification Handler Interface
Define common handler protocol:
```python
class NotificationHandler(Protocol):
async def send(
self,
channel: NotificationChannel,
message: dict[str, Any],
event: NotificationEvent,
) -> None:
\"\"\"Send notification through this handler\"\"\"
async def validate_config(self, config: dict[str, Any]) -> None:
\"\"\"Validate channel configuration\"\"\"
async def test(self, channel: NotificationChannel) -> bool:
\"\"\"Send test notification\"\"\"
```
### 2. Webhook Handler
HTTP POST/PUT webhook delivery:
```python
class WebhookHandler:
async def send(self, channel, message, event):
config = channel.config
# Required: url
# Optional: method, headers, auth, timeout, retry
# Build request
# Send with retry logic
# Handle response
async def validate_config(self, config):
# Validate: url, method, headers format
# Check URL accessibility (optional)
```
**Config schema**:
```json
{
"url": "https://example.com/webhook",
"method": "POST",
"headers": {
"Authorization": "Bearer token",
"Content-Type": "application/json"
},
"timeout": 30,
"retry": {
"max_attempts": 3,
"backoff": "exponential"
}
}
```
### 3. SMTP Handler
Email delivery via SMTP:
```python
class SMTPHandler:
async def send(self, channel, message, event):
config = channel.config
# Required: host, port, from, to
# Optional: tls, auth (username/password), subject_prefix
# Build email (HTML + plain text)
# Send via SMTP
async def validate_config(self, config):
# Validate email addresses
# Check SMTP connectivity (optional)
```
**Config schema**:
```json
{
"host": "smtp.gmail.com",
"port": 587,
"from": "noreply@lablup.com",
"to": ["admin@lablup.com"],
"tls": true,
"auth": {
"username": "user",
"password": "pass"
},
"subject_prefix": "[Backend.AI]"
}
```
### 4. Slack Handler
Slack webhook integration:
```python
class SlackHandler:
async def send(self, channel, message, event):
config = channel.config
# Required: webhook_url
# Optional: channel, username, icon_emoji
# Convert to Slack message format
# Post to webhook
async def validate_config(self, config):
# Validate webhook_url format
# Test webhook (optional)
```
**Slack message format**:
```json
{
"channel": "#dev-notifications",
"username": "Backend.AI",
"icon_emoji": ":robot_face:",
"attachments": [{
"color": "good",
"title": "Task completed",
"text": "...",
"fields": [...],
"footer": "Backend.AI Notification"
}]
}
```
### 5. Discord Handler
Discord webhook integration:
```python
class DiscordHandler:
async def send(self, channel, message, event):
config = channel.config
# Required: webhook_url
# Optional: username, avatar_url
# Convert to Discord embed format
# Post to webhook
```
**Discord embed format**:
```json
{
"username": "Backend.AI",
"embeds": [{
"title": "Task completed",
"description": "...",
"color": 3066993,
"fields": [...],
"footer": {"text": "Backend.AI"}
}]
}
```
### 6. Template Rendering
Implement Jinja2 template rendering:
```python
class TemplateRenderer:
def render(self, template_str: str, event: NotificationEvent) -> dict:
template = jinja2.Template(template_str)
context = {
**event.context,
"event_id": event.event_id,
"timestamp": event.timestamp,
"subject": event.subject,
"severity": event.severity,
**event.metadata,
}
rendered = template.render(**context)
return json.loads(rendered)
```
### 7. Handler Registry Integration
Register all handlers:
```python
# In worker initialization
registry = HandlerRegistry()
registry.register("webhook", WebhookHandler())
registry.register("smtp", SMTPHandler())
registry.register("slack", SlackHandler())
registry.register("discord", DiscordHandler())
```
## Technical Requirements
1. **Retry Logic**: Exponential backoff for transient failures
1. **Timeout**: Configurable per handler
1. **Error Handling**: Proper exception hierarchy
1. **Logging**: Comprehensive logging for debugging
1. **Testing**: Unit tests with mocks
## Acceptance Criteria
- [ ] WebhookHandler with retry and auth support
- [ ] SMTPHandler with TLS and authentication
- [ ] SlackHandler with proper message formatting
- [ ] DiscordHandler with embed support
- [ ] Template renderer with Jinja2
- [ ] Config validation for each handler
- [ ] Test notification functionality
- [ ] Retry logic with exponential backoff
- [ ] Comprehensive error handling
- [ ] Unit tests for each handler
- [ ] Integration tests with mock servers
## Related Issues
Epic: BA-302
Depends on: BA-2861 (Core infrastructure)
JIRA Issue: BA-2865
Contributor guide
Assessment
This issue has not been assessed yet.