ATHELOS
Automation

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=true

Field 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 — true for implicit TLS, false for STARTTLS

Restart the worker after changing env vars:

pkill -f "worker"
pnpm dev:worker

Configuring webhooks

Add to apps/web/.env.local:

WEBHOOK_URL=https://hooks.slack.com/services/...
WEBHOOK_SECRET=your-hmac-secret

The 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 32

The 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.0

Verifying 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.

EventDefault notification
finding.criticalYes — email + webhook
report.deliveredYes — email
delivery.viewedWebhook only
invoice.paidEmail
invoice.overdueEmail (daily check)
cert.expiringEmail (weekly check)
backup.completedNo
backup.failedYes — 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:00

Events 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