MainSDK
Search⌘K
Interfaces

Reliability and operations

Alerts

alerts

Route SLO burn, downed dependencies, security detections and your own alerts to email, Slack, PagerDuty, Opsgenie or a webhook, once.

Building for 0.4ships as a beta APIPhase 1 · Core, public betaSpec alertsFree in your app, Apache-2.0

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
Updated 2026-10-28

Route SLO burn, downed dependencies, security detections and your own alerts to email, Slack, PagerDuty, Opsgenie or a webhook, once.

At a glance

alerts:
  min: warning
  recipients:
    oncall:
      - { driver: pagerduty, key: secret:PAGERDUTY_ROUTING_KEY }
      - { driver: slack, webhook: secret:SLACK_ALERTS_WEBHOOK }
  routes:
    - { match: { severity: critical }, to: [oncall], escalate: [{ after: 15m, to: [oncall] }] }
    - { to: [oncall] }
  quiet: { from: "22:00", to: "08:00", tz: Asia/Kolkata }

Calls

  • alerts.raise({ key, severity, title, attrs?, runbook? })
  • alerts.resolve(key)
  • alerts.ack(id)
  • alerts.get(id)
  • alerts.list({ state?, source?, cursor?, limit? })

Guarantees

Whatever the driver.

  • One message per open alert, however many instances raise it
  • Critical alerts go at once; others wait out quiet hours as a digest
  • Never on the request path; sends run as jobs with retries
  • No personal data in an alert; provider dedupe keys are keyed hashes

Drivers

1 drivers. Select one to see its line in main.yaml.

main.yamlBuilt in (sends through notify and email drivers) (default)
alerts:
  driver: builtin

Change this one line to swap providers; your calls stay the same.Built-in and local drivers integration

Codes

CodeWhere it appearsMeaning
MAIN-ALERT-0001Checkmainsdk check, before deployAn alerts value is invalid: an unknown driver, source or severity, a route to an undeclared recipient, a quiet-hours time or time zone that doesn't parse, or an escalation after shorter than group.wait
MAIN-ALERT-0002Checkmainsdk check, before deployProduction has an alerts section, but no route sends critical alerts to a recipient
MAIN-ALERT-0003Checkmainsdk check, before deployA webhook recipient's host isn't in network.allow, or a recipient key or webhook URL is written as a literal
MAIN-ALERT-0010RuntimeError responses and logsAn alert was raised or resolved; the log record carries the alert
MAIN-ALERT-0011RuntimeError responses and logsA recipient failed after its last retry; the others were still sent to
MAIN-ALERT-0012RuntimeError responses and logsThe runtime store didn't answer; dedupe and escalation fell back to this instance
MAIN-ALERT-0013RuntimeError responses and logsalerts.raise refused or trimmed an input: a key over 200 characters, more than 20 attrs, or an attr outside telemetry@1
MAIN-ALERT-0014RuntimeError responses and logsalerts.ack refused: the caller lacks alerts.manage (403)
MAIN-ALERT-0015RuntimeError responses and logsA recipient passed max-per-hour; the rest of the hour arrives as one summary

Features

Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.

FeaturePhaseMilestoneSpec ticket
alerts section in main.yaml with its checksPhase 10.4alerts#contract
raise, resolve, ack and list with dedupe by keyPhase 10.4alerts#core
Routes, grouping, repeat and storm limitsPhase 10.4alerts#routing
Quiet hours and escalationPhase 10.4alerts#quiet-escalation
Slack, PagerDuty, Opsgenie, webhook and email recipientsPhase 10.4alerts#drivers
Delivery as jobs off the request pathPhase 10.4alerts#delivery
Health, security, resources, jobs and error sources (the backup source is planned)Phase 10.4alerts#sources
Routes in mainsdk info and mainsdk alerts testPhase 10.4alerts#cli

MainSDK Cloud

Everything above is free in your app. Cloud adds these on top, by plan:

PlanWhat it adds
FleetAlert routing to owners across every app, with agent fix pull requests (spec fleet-observe)

Quick start

main.yaml
alerts:
  min: warning
  recipients:
    oncall:
      - { driver: pagerduty, key: secret:PAGERDUTY_ROUTING_KEY }
      - { driver: slack, webhook: secret:SLACK_ALERTS_WEBHOOK }
  routes:
    - { match: { severity: critical }, to: [oncall], escalate: [{ after: 15m, to: [oncall] }] }
    - { to: [oncall] }
  quiet: { from: "22:00", to: "08:00", tz: Asia/Kolkata }
app/example.ts
import { alerts } from "mainsdk";

// one message per open key, however often it is raised; routes in main.yaml pick who hears it
await alerts.raise({ key: "import-failed", severity: "warning", title: "Nightly import failed", runbook: "https://acme.dev/runbooks/import" });

await alerts.resolve("import-failed");

Configuration

See the alerts.* rows in main.yaml reference. mainsdk check reports an invalid value as MAIN-ALERT-0001, a production app with no route for critical alerts as MAIN-ALERT-0002, and a literal key or a webhook host outside network.allow as MAIN-ALERT-0003.

What you get

  • One message per open alert, however many instances raise it
  • Critical alerts go at once; others wait out quiet hours as a digest
  • Never on the request path; sends run as jobs with retries
  • No personal data in an alert; provider dedupe keys are keyed hashes

Calls

CallWhat it does
alerts.raise({ key, severity, title, attrs?, runbook? })Opens an alert, or updates the open one with the same key, and routes it once.
alerts.resolve(key)Closes the open alert with that key.
alerts.ack(id)Acknowledges an alert and stops its escalation; needs the alerts.manage permission.
alerts.get(id)Returns one alert.
alerts.list({ state?, source?, cursor?, limit? })Pages through alerts, filtered by state or source.

Sources

The SDK raises alerts from these sources. Limit them with alerts.from.

SourceRaisesKey
healthA dependency in health.critical is down (critical); SLO burn, fast (critical) or slow (warning)dependency:<name>, slo:<slo>:<rule>
resourcesThe instance enters shed (warning)resources:shed
securitySecurity events at high (warning) and critical (critical)security:<type>
jobsA job moves to the dead-letter queue (MAIN-JOB-0002, warning)job:<name>
errorsA new error group or an error spike, from the error-reporting porterror:<fingerprint>, error-spike:<fingerprint>
appalerts.raisethe caller's key

The backup source (a failed or overdue restore drill) is planned. alerts.from accepts the name, but nothing raises from it yet.

Health and resource alerts resolve when the cause clears. Security, job and new-error alerts never resolve themselves; a person acknowledges them.

Recipients and destinations

A recipient is a named group of destinations in alerts.recipients. Each destination uses one of five drivers: email, slack, pagerduty, opsgenie or webhook. Keys and webhook URLs are written as secret:NAME, never as literals (MAIN-ALERT-0003). A route sends an alert to recipients; an escalation sends an open, unacknowledged alert to another recipient after a set time. Quiet hours hold warnings and info as one digest; critical alerts always go at once.

Drivers

DriverProvider
builtinBuilt in (sends through notify and email drivers)default

CLI

text
mainsdk alerts test oncall --env prod
mainsdk info

alerts test sends a test alert, marked as a test, through every driver of the recipient and prints each result. mainsdk info shows the recipients by name and driver (never their keys), the routes and the open alerts.

Errors and checks

alerts.raise and alerts.resolve never wait on a provider. Sends run as jobs with retries. If a recipient fails after its last retry, the others are still sent to (MAIN-ALERT-0011). If the runtime store does not answer, dedupe and escalation fall back to the instance (MAIN-ALERT-0012) and the app keeps serving.

  • MAIN-ALERT-0001
  • MAIN-ALERT-0002
  • MAIN-ALERT-0003
  • MAIN-ALERT-0010
  • MAIN-ALERT-0011
  • MAIN-ALERT-0012
  • MAIN-ALERT-0013
  • MAIN-ALERT-0014
  • MAIN-ALERT-0015

Each code's cause and fix is in check codes.

Swapping drivers

alerts has one driver, builtin, which sends through your notify and email drivers. To change where an alert goes, change the destination under a recipient (email, slack, pagerduty, opsgenie or webhook) and keep the calls as they are.

main.yaml
alerts:
  recipients:
    oncall:
      - { driver: opsgenie, key: secret:OPSGENIE_KEY }   # was pagerduty

See Swap a driver.

Testing

createTestApp runs alerts on the fake clock and captures outbound requests, so no provider is called. A raise with the key of an open alert folds into it.

lib/alerts.test.ts
import assert from "node:assert/strict";
import { createTestApp } from "@mainsdk/testing";

const app = createTestApp({
  yaml: `schema: 1
project: prj_alerts
alerts:
  recipients:
    oncall:
      - { driver: slack, webhook: secret:SLACK }
  routes:
    - { to: [oncall] }
`,
  secrets: { SLACK: "https://hooks.slack.com/services/T0/B0/xyz" },
});

await app.alerts.raise({ key: "import-failed", severity: "warning", title: "Import failed" });
await app.alerts.raise({ key: "import-failed", severity: "warning", title: "Import failed" });

const open = await app.alerts.list({ state: "open" });
assert.equal(open.items.length, 1);   // the second raise folded into the first
assert.equal(open.items[0]?.count, 2);

Next

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet