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:
| Kind | Observed | Limit |
|---|---|---|
storage | Database size in bytes | Plan storage limit |
connections | Current connections to the database | Plan 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.
| Kind | Opens when | Resolves when |
|---|---|---|
backup | A backup job fails after exhausting its retries | The next backup succeeds |
backup_stale | A project with an active backup schedule has no verified backup in the last 26 hours | A verified backup lands |
unreachable | The database is marked running but fails the health-check connection on two consecutive sweeps | A health check connects, or the database pauses normally |
kv_unreachable | The project's K/V store fails the reachability probe on its public endpoint on two consecutive sweeps | The store answers again |
pooler_handshake | Clients keep failing to connect through the pooled endpoint at handshake (unsupported startup parameters, authentication failures) while Postgres itself is healthy | A 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.
| Kind | Opens when | Where to start |
|---|---|---|
cache_hit | The buffer cache hit ratio stays below 90% | Working set outgrowing memory - see Observability |
blocked_queries | Queries sit blocked on locks across the window | Find the blocking transaction and close it |
deadlocks | Deadlocks were detected in the interval | Two transactions taking the same locks in different orders |
vacuum | Dead tuples accumulate faster than autovacuum reclaims them | Often caused by long_transaction below |
long_transaction | A transaction has been open for over 15 minutes | Close it; while it is open, nothing it might still see can be reclaimed |
subtransactions | Backends have overflowed the 64-entry savepoint cache | Reduce nested transactions or EXCEPTION blocks on hot paths |
oom_kill | The database was stopped for exceeding its memory limit | Explains an outage unreachable already reported |
temp_spill | The database sustains writing temporary files | A 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
| Transition | Threshold |
|---|---|
Opens at warning | usage ≥ 80% |
Opens at / escalates to critical | usage ≥ 95% |
critical downgrades to warning | usage drops below 90% |
| Alert resolves | usage 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.resolvednotification.
Delivery channels
-
Dashboard - open alerts surface on the project, alongside the Observability gauges that triggered them.
-
Webhooks - your org's webhook endpoints receive
alert.triggeredandalert.resolvedevents (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.resolvedcarries the samedataplusresolved_at. Values are bytes forstorageand a connection count forconnections. -
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"]
}'PUTreplaces 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 a400.alert_emails_enabled: falsestops 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}/acknowledgeBoth 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 nullWhen 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.