Notifications
Email and webhook alerts on critical events.
Notifications
ATHELOS can notify you about events by email or webhook. Notifications are opt-in — nothing sends by default.
What notifications are for
Automation should tell you when something needs attention:
- A critical finding was added to an active engagement
- A client opened a report you delivered
- A backup failed
- An invoice went past its due date
- A cert is within 90 days of expiry
Notifications close the loop between ATHELOS and you, without you having to check the dashboard.
Two channels
ATHELOS supports two delivery channels:
Email (SMTP)
Sends notifications to an email address. You provide an SMTP server — your own, or a transactional provider like Postmark, Resend, SendGrid, or Mailgun.
Webhook
Sends an HTTP POST to a URL you provide. Payload is JSON. Signature is HMAC-SHA256. Useful for integrating with Slack, Discord, or your own automation.
Both channels can be enabled at the same time.
Configuring SMTP
Set these in apps/web/.env.local:
SMTP_HOST=smtp.postmarkapp.com
SMTP_PORT=587
SMTP_USER=your-smtp-username
SMTP_PASSWORD=your-smtp-password
SMTP_FROM="ATHELOS <noreply@yourdomain.com>"
SMTP_TO="you@yourdomain.com"
SMTP_SECURE=trueField meanings:
- SMTP_HOST — the server hostname
- SMTP_PORT — usually 587 (STARTTLS) or 465 (implicit TLS)
- SMTP_USER / SMTP_PASSWORD — SMTP credentials
- SMTP_FROM — the "from" address shown to recipients
- SMTP_TO — where notifications go
- SMTP_SECURE —
truefor implicit TLS,falsefor STARTTLS
Restart the worker after changing env vars:
pkill -f "worker"
pnpm dev:workerConfiguring webhooks
Add to apps/web/.env.local:
WEBHOOK_URL=https://hooks.slack.com/services/...
WEBHOOK_SECRET=your-hmac-secretThe secret is used to sign each payload. Recipients verify the signature to confirm the request came from ATHELOS.
To generate a secret:
openssl rand -hex 32The webhook payload
Every webhook POST has this shape:
{
"event": "finding.critical",
"timestamp": "2026-09-29T12:34:56Z",
"source": "athelos",
"data": {
"findingId": "uuid",
"engagementId": "uuid",
"severity": "critical",
"title": "SQL Injection in login form"
}
}Headers:
Content-Type: application/json
X-ATHELOS-Signature: <hmac-sha256-hex>
User-Agent: ATHELOS-Worker/1.0Verifying the signature
On the receiving end, verify the HMAC:
import { createHmac, timingSafeEqual } from "crypto";
function verifySignature(payload: string, signature: string, secret: string) {
const expected = createHmac("sha256", secret).update(payload).digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}Use constant-time comparison to prevent timing attacks.
Event types
Every event from the event bus can trigger a notification, if enabled.
| Event | Default notification |
|---|---|
finding.critical | Yes — email + webhook |
report.delivered | Yes — email |
delivery.viewed | Webhook only |
invoice.paid | |
invoice.overdue | Email (daily check) |
cert.expiring | Email (weekly check) |
backup.completed | No |
backup.failed | Yes — email + webhook |
Defaults are configurable. See Settings → Notifications in the web UI.
Settings page
The notification preferences page lets you:
- Enable or disable email
- Enable or disable webhooks
- Choose which events trigger notifications
- Set quiet hours (no notifications during a window)
- Send a test notification
Every change is logged to the audit trail.
Per-event configuration
For each event type:
- Email — on/off
- Webhook — on/off
- Priority — normal, high (bypasses quiet hours)
The defaults ship with a sensible starting configuration. Adjust as you learn which events matter.
Quiet hours
Set a window where notifications are suppressed:
QUIET_HOURS_START=22:00
QUIET_HOURS_END=07:00Events during quiet hours are:
- Queued for the next window (normal priority)
- Delivered immediately (high priority)
Use high priority for events that can't wait — critical findings, backup failures.
Delivery guarantees
Notifications are delivered at least once. If the webhook receiver is down, ATHELOS retries with exponential backoff:
- 1 second
- 4 seconds
- 16 seconds
- 1 minute
- 5 minutes
- 30 minutes
After 6 failed attempts, the job moves to the dead letter queue and stops retrying.
Idempotency — because a notification may be retried, receivers should
be idempotent. Include the event ID in any downstream action so duplicates
are handled.
Failed notifications
Check the failed queue in Redis:
docker exec soc_redis redis-cli -a <password> --scan --pattern "bull:notify:*:failed"Or use Bull Board for a visual view.
Testing
From Settings → Notifications, click Send test notification.
This sends one email and one webhook (if configured) with a test payload. Verify both arrive. If they don't, check:
- SMTP credentials
- Webhook URL reachability
- Worker is running
- Logs:
journalctl -u athelos-worker -n 100
Common patterns
Slack
Create a Slack incoming webhook. Paste the URL into WEBHOOK_URL. Every
notification posts as a Slack message.
Format the message with Slack's block kit if you want rich output. The default JSON works but is plain.
Discord
Discord accepts webhooks too. The payload format differs from Slack —
you may need a small adapter service, or use a tool like n8n to
transform.
Email digest
For less important events, batch them into a daily digest. Configure
NOTIFY_DIGEST=true and set DIGEST_HOUR=08:00. Low-priority events
queue up and are sent once per day.
PagerDuty or Opsgenie
Integrate via webhook. Transform the payload in an adapter to match their event format.
Turning off notifications
Set EMAIL_NOTIFICATIONS=false and WEBHOOK_NOTIFICATIONS=false in the
env file. Or use the Settings page to toggle per event.
Nothing sends by default — if you never configured SMTP or a webhook, notifications silently no-op.
Where to go next
- Event bus — what triggers notifications
- Scheduled backups — backup events
- Settings: notifications — the UI