Docs · Features

Alerting

Unravel can notify you when something breaks: a brand-new exception, or an error rate crossing a threshold. Alerts carry the debugging context with them (throw site, route, affected user, a deep link to the exact window that fired), so the notification is the start of the fix rather than a red light.

Alerting is available on paid plans (Solo and above). Configure it per project under Settings → Alerting in the dashboard.

Channels

A channel is a destination for alerts. Create channels first, then attach them to rules. Every channel has a Send test button so you can verify delivery before anything is on fire.

Email

Up to 10 recipient addresses per channel. Alerts arrive from Unravel with the full context inline.

Slack

Alerts post to a Slack channel through an incoming webhook, formatted with Block Kit: structured sections with buttons linking back to the dashboard.

Slack hides incoming webhooks behind an app, so the one-time setup is:

  1. Go to api.slack.com/apps and click Create New App → From scratch. Name it Unravel and pick your workspace.
  2. Under Basic Information → Display Information, set the name to Unravel and upload our icon (download) so alerts show up branded in your channel. Optionally set the short description to “I unravel the bugs before they unravel you.” and the background color to #1c1b18.
  3. In the app's sidebar, open Incoming Webhooks and toggle them on.
  4. Click Add New Webhook to Workspace and choose the channel alerts should land in.
  5. Copy the generated webhook URL (https://hooks.slack.com/services/…) and paste it into a new Slack channel in Unravel.

There's no shared Unravel app in the Slack marketplace yet, so this private per-workspace app is how you connect; the name and icon you set in step 2 are what your team sees on every alert. The app needs no review or distribution.

Webhook

A generic JSON POST to any HTTPS endpoint you control, for wiring alerts into your own automation. The payload is a stable contract:

{
  "subject": "⚠️ 31 exceptions in 5 min — my-app/production",
  "heading": "31 exceptions in 5 min",
  "project": "my-app",
  "env": "production",
  "kind": "threshold",
  "severity": "warning",
  "ruleName": "Production error spike",
  "firedAt": "2026-06-20T14:32:00+00:00",
  "sections": [
    {
      "title": null,
      "text": "31 exceptions in 5 min — threshold is 25.",
      "meta": "Top: RuntimeException (18), PDOException (9), TypeError (4).",
      "code": null,
      "linkUrl": "https://app.unravel.run/p/my-app/production/exceptions?from=…&to=…",
      "linkLabel": "View exceptions"
    }
  ]
}

The next two examples abbreviate the shared top-level fields (project, env, ruleName, firedAt) shown above to focus on what differs.

A new-exception alert carries the debugging context in the section and a link straight to the failing trace:

{
  "subject": "🔴 New exception: PaymentFailed — my-app/production",
  "heading": "New exception: PaymentFailed",
  "kind": "new_exception",
  "severity": "critical",
  "sections": [
    {
      "title": "App\\Exceptions\\PaymentFailed",
      "text": "Card declined for order #4821",
      "meta": "at app/Services/Checkout.php:42 · POST /checkout · user 1337",
      "code": "#0 app/Services/Checkout.php(42): charge()\n#1 …",
      "linkUrl": "https://app.unravel.run/p/my-app/production/traces/9f2c…",
      "linkLabel": "Open trace"
    }
  ]
}

When a threshold drops back under its limit you get a recovery notice with severity: "recovered":

{
  "subject": "✅ Recovered: exceptions back to normal — my-app/production",
  "heading": "Recovered: exceptions back to normal",
  "kind": "threshold",
  "severity": "recovered",
  "sections": [
    {
      "text": "Back to normal — 4 exceptions in 5 min, under the threshold of 25.",
      "linkUrl": "https://app.unravel.run/p/my-app/production/exceptions?from=…&to=…",
      "linkLabel": "View exceptions"
    }
  ]
}
  • heading leads with the fact that fired; ruleName is the name of your rule.
  • kind is the rule type (new_exception or threshold); severity is its visual level (critical, warning, info, or recovered, the last for a threshold that has dropped back under its limit).
  • firedAt is an ISO-8601 timestamp of when the alert fired.
  • Each section carries title (a lead, e.g. the exception type), text (the sentence), meta (a context line: throw site · route · user), and code (the stacktrace head); any of these, plus linkUrl / linkLabel, may be null.
  • sections is a list: when several incidents fire close together they are batched into one delivery, one section each.
  • Respond with a 2xx status. Anything else counts as a failed delivery and is retried.

Requirements: the URL must be https:// and resolve to a public address (private and internal network ranges are rejected). Redirects are not followed, and the request times out after 5 seconds.

Rule types

Rules are scoped to one environment of a project: a production rule never fires for staging noise.

New exception

Fires the first time an exception fingerprint is ever seen in the project and environment. The alert includes the exception type and message, the throw site (file:line), the HTTP route and user it hit, the head of the stacktrace, and a deep link that opens the exact failing trace: the full request timeline in the debugger, one click from the notification.

When you create the rule, Unravel backfills everything it has already seen, so your existing exception history won't replay as a flood of "new" alerts. Only genuinely novel exceptions fire from that point on.

Each fingerprint fires exactly once, so there is no cooldown to configure. A burst of distinct new exceptions (a bad deploy, say) is batched into a single message rather than one notification per exception.

Threshold

Fires when a count crosses a limit over a sliding window: N or more exceptions / failed jobs / HTTP 5xx responses in the last X minutes. Rules are evaluated every minute.

The alert reports the count, a top-3 breakdown of what's driving it (exception types, job classes, or routes), and a deep link to the dashboard scoped to the exact window that fired.

A threshold rule fires once when it crosses the limit, then sends a recovery notice when the metric drops back under it, so a sustained incident is one alert and one all-clear instead of a page every minute. After it recovers, the cooldown (default 60 minutes) is a re-arm delay before the rule can fire again, so a flapping metric doesn't alternate alert/recovery endlessly.

Delivery

Alerts are delivered within about a minute of the condition firing. Failed deliveries are retried per channel: a flaky webhook doesn't block your email. Every firing is recorded under Settings → Alerting → History, including the delivery status of each message.

Rules can be paused and resumed at any time; paused rules don't evaluate and don't accumulate alerts. To silence a rule temporarily without pausing it, use Mute (1 hour, 24 hours, or 7 days): it stops firing until the timer expires or you unmute it.