CapyDB/ docs
Guides

Alerts

Usage threshold alerts on storage and connections, health alerts on backups and reachability, and performance advisories - delivered to the dashboard, your webhooks, and email recipients you choose.

What gets alerted

CapyDB continuously evaluates two gauges per project against the plan's limits:

KindObservedLimit
storageDatabase size in bytesPlan storage limit
connectionsCurrent connections to the databasePlan connection budget

There is at most one open alert per project and kind. Severity moves in place on the open alert; a resolved alert closes and a future breach opens a new one.

Health alerts

These are not thresholds: each opens when something that should be working is not - at critical, except pooler_handshake, which opens at warning - and resolves on its own once it is. Unlike the advisories below, they notify through the same channels as the usage alerts.

KindOpens whenResolves when
backupA backup job fails after exhausting its retriesThe next backup succeeds
backup_staleA project with an active backup schedule has no verified backup in the last 26 hoursA verified backup lands
unreachableThe database is marked running but fails the health-check connection on two consecutive sweepsA health check connects, or the database pauses normally
kv_unreachableThe project's K/V store fails the reachability probe on its public endpoint on two consecutive sweepsThe store answers again
pooler_handshakeClients keep failing to connect through the pooled endpoint at handshake (unsupported startup parameters, authentication failures) while Postgres itself is healthyA window passes with no handshake failures

A single failed probe never opens unreachable or kv_unreachable - a paused database waking up fails exactly one. pooler_handshake usually means a client-side configuration problem; see Connection pooling.

Performance advisories

Alongside the gauges above, CapyDB derives warning-severity advisories from the metrics it samples for every project. They surface in the dashboard, the API and capydb alerts list only - they never send email or fire a webhook, because each one is a hint to act on when you are already looking, not an incident.

KindOpens whenWhere to start
cache_hitThe buffer cache hit ratio stays below 90%Working set outgrowing memory - see Observability
blocked_queriesQueries sit blocked on locks across the windowFind the blocking transaction and close it
deadlocksDeadlocks were detected in the intervalTwo transactions taking the same locks in different orders
vacuumDead tuples accumulate faster than autovacuum reclaims themOften caused by long_transaction below
long_transactionA transaction has been open for over 15 minutesClose it; while it is open, nothing it might still see can be reclaimed
subtransactionsBackends have overflowed the 64-entry savepoint cacheReduce nested transactions or EXCEPTION blocks on hot paths
oom_killThe database was stopped for exceeding its memory limitExplains an outage unreachable already reported
temp_spillThe database sustains writing temporary filesA sort or hash does not fit in work_mem - raise it per statement, not globally

Advisories are workload-scoped: they close on their own when the condition clears, and when a database scales to zero (its load no longer exists).

Thresholds and hysteresis

TransitionThreshold
Opens at warningusage ≥ 80%
Opens at / escalates to criticalusage ≥ 95%
critical downgrades to warningusage drops below 90%
Alert resolvesusage drops below 75%

The trigger and resolve thresholds are deliberately apart (hysteresis), so usage hovering at exactly 80% cannot flap an alert open and closed on every evaluation sweep.

Notification rules

  • A new alert notifies immediately.
  • An escalation (warning → critical) notifies immediately.
  • An alert that simply stays open re-notifies at most once every 24 hours.
  • Resolution sends one alert.resolved notification.

Delivery channels

  1. Dashboard - open alerts surface on the project, alongside the Observability gauges that triggered them.

  2. Webhooks - your org's webhook endpoints receive alert.triggered and alert.resolved events (standard signed envelope):

    {
      "id": "evt_...",
      "type": "alert.triggered",
      "created_at": "2026-06-10T08:00:00.000Z",
      "data": {
        "alert_id": "alr_...",
        "kind": "storage",
        "severity": "critical",
        "observed_value": 4928307200,
        "limit_value": 5368709120,
        "project_id": "prj_...",
        "triggered_at": "2026-06-10T08:00:00.000Z"
      }
    }

    alert.resolved carries the same data plus resolved_at. Values are bytes for storage and a connection count for connections.

  3. Email - alert notifications go by plain-text email to the organization's billing email, when one is set, and to any extra alert recipients you add (see Email preferences). With neither, no email is sent; webhooks and the dashboard still work. Emails are queued and retried with backoff if delivery fails, so a transient mail outage delays an alert email rather than losing it.

Email preferences

An organization can switch alert emails off, and add up to 10 extra recipients each for alert emails and for billing notices (suspension, read-only, offline, restored), on top of the billing email.

In the dashboard this is the Notifications tab of the workspace settings (next to API keys and Webhooks). Workspace admins edit it: a Send alert emails switch and two recipient lists, one address per line (commas and semicolons also separate), checked as you type. Everyone else in the workspace sees the same page read-only. A workspace that never saved preferences shows that it is using the defaults; otherwise the page shows when they were last saved.

Over the API:

# Read (organizations:read; not available to project-scoped API keys)
curl -s -H "Authorization: Bearer $CAPYDB_API_KEY" \
  https://capydb.dev/api/capydb/v1/organizations/$ORG_ID/notification-preferences

# Replace (organization admin, or an organization-wide API key minted by one)
curl -X PUT https://capydb.dev/api/capydb/v1/organizations/$ORG_ID/notification-preferences \
  -H "Authorization: Bearer $CAPYDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "alert_emails_enabled": true,
    "alert_email_recipients": ["oncall@example.com"],
    "billing_email_recipients": ["finance@example.com"]
  }'
  • PUT replaces the whole object: every field is required, so send the current lists back when you change one. Duplicates are removed; more than 10 addresses in a list is a 400.
  • alert_emails_enabled: false stops usage, backup and reachability alert emails. Alerts still open in the dashboard and fire webhooks.
  • Billing notices cannot be switched off - only their extra recipients are configurable. Without a billing email they go to the organization's admins.
  • An organization that never saved preferences gets the defaults: alert emails on, no extra recipients, and updated_at: null.

Changes are recorded in the organization's audit trail as organization.notification_preferences_updated.

Acknowledging

Acknowledging records that a human has seen the alert - acknowledged_at is set once (first acknowledgement wins) and the dashboard renders it as seen. It is visual only: an acknowledged alert keeps re-notifying on the 24-hour cadence until usage actually drops and it resolves. The way to silence an alert is to fix the usage or raise the limit.

API

# Open alerts plus alerts resolved in the last 30 days
GET  /v1/projects/{projectID}/alerts

# Mark one as seen (idempotent)
POST /v1/projects/{projectID}/alerts/{alertID}/acknowledge

Both are available to any org member. See the Observability API reference for the full ProjectAlert shape.

CLI

capydb alerts list                  # uses the linked project; --project to target another
capydb alerts ack alr_123abc        # acknowledge by id
capydb alerts list --output json    # machine-readable, lists always [] not null

When an alert fires

  • Storage: delete unused data, VACUUM, or move up a plan. Remember that imports are blocked when the source exceeds the plan limit anyway - the alert is your early warning before that wall.
  • Connections: app traffic belongs on the pooled URL; hunt for oversized client-side pools and long-idle direct sessions. See Connection pooling.