Alerts
Onelo opens an incident when a feature's error rate spikes above its alert rule. Configure email recipients per app and tune thresholds per feature.
Auto-rule per feature
The first time the SDK reports an event for a new featureName, a row is created in monitor_alert_rules with a sensible default. You never have to provision rules manually.
| Field | Default | Notes |
|---|---|---|
| error_threshold | 0.10 | Fraction; 0.10 = 10% errors over the window. The auto-rule trigger sets 0.10 (the raw column default is 0.05). |
| window_mins | 15 | Sliding window in minutes. |
| enabled | true | Set to false to silence alerts for this feature without deleting the rule. |
| last_alerted_at | null | When the rule last opened or reopened an incident. Written by the scheduler. |
| last_alert_outcome | null | What happened to that alert's e-mail: sent (the mail server accepted it for every recipient), send_failed (it refused at least one), alerts_off, no_recipients or daily_cap (the free plan's daily alert-email limit). The Configure alert panel and MCP monitor.get_alert_rule show it as a sentence. |
Source: supabase/migrations/20260426000002_monitor_alert_rules_auto.sql.
Configure thresholds
Open the dashboard → Monitor → click a feature row → Configure alert. Edit error_threshold and window_mins, or flip enabled to false to disable alerts entirely for that feature. Changes apply on the next evaluation tick.
Email config (per app)
Recipients and templates live in the monitor_alert_config table — one row per app. Set them with PUT /api/monitor/alert-config: that endpoint is the only way to change them today. The dashboard shows the stored recipients beside the switches, but its switches only turn the alerts on and off. The PUT writes only the fields your request carries: anything you omit is left as it is, so the dashboard's on/off switches cannot overwrite the recipients or templates you configured here. The one exception is that turning an alert on while alertEmails is still empty stores the signed-in account's address, so the alert has somewhere to go.
| Field | Type | Description |
|---|---|---|
| alertEmails | string[] | List of recipient email addresses. |
| alertEnabled | bool | Master switch for outbound alert emails. |
| recoveryEnabled | bool | When true, send an “all clear” email once error rate drops back below threshold. |
| alertSubject | string | Templated subject — supports {{app_name}}, {{feature_name}}, {{error_rate}}, {{errors}}, {{total}}, {{window_mins}}, {{incident_number}}, {{last_error_block}}, {{affected_str}}, {{dashboard_url}}. |
| alertHtml | string | Templated HTML body — same variables as alertSubject. |
| recoverySubject | string | Templated subject for recovery email — supports {{app_name}}, {{feature_name}}, {{error_rate}}, {{incident_number}}, {{dashboard_url}}. |
| recoveryHtml | string | Templated HTML body for recovery email — same variables as recoverySubject. |
Include {{app_name}} in your subject and body — if you manage multiple Onelo apps, recipients need to know which one the alert is about. The dashboard surfaces a warning on the app's Notifications page if a custom template is missing this variable.
{
"appId": "b3c4d5e6-…",
"alertEmails": ["[email protected]", "[email protected]"],
"alertEnabled": true,
"recoveryEnabled": true,
"alertSubject": "[Onelo] {{app_name}} · {{feature_name}} error rate {{error_rate}}",
"alertHtml": "<p>{{app_name}} / {{feature_name}}<br/>Last error: {{last_error_block}}</p>"
}Incident lifecycle
When a rule fires, Onelo opens a row in monitor_incidents. Numbering is per-workspace — unique across all apps in your tenant — and assigned by the next_incident_number(p_app_id) RPC, so your workspace's first incident is INC-0001 (a second app continues the same sequence).
| Field | Description |
|---|---|
| peak_error_rate | Highest error rate observed during the incident. |
| last_error | Most recent error string sampled while the incident is open. For an HTTP monitor it names what the check saw: the status and a short body snippet (HTTP 400 (expected 200): …), a missing bodyContains text, or the network error (timeout after 10 s). |
| affected_users | Distinct signed-in users whose events failed in the alert window. One person reloading the page counts once. It only grows while the incident stays open. |
| anonymous_sessions | Distinct sessions with no signed-in user whose events failed in the alert window. Server-side SDK processes (Python, Go) are not counted. HTTP monitor checks add to neither count. |
| priority | low / medium / high. Defaults to high. Editable. |
| description | Free-form incident description, editable in the dashboard. |
| notes | Separate free-form operator notes, editable in the dashboard. |
| alert_sent | Boolean, informational. True once an alert email actually went out for the incident's latest opening (a reopen sets it again from that cycle). It stays false when alert emails are off, nobody is listed, the daily cap stopped the send or the mail server refused every recipient; the rule's last_alert_outcome says which. Duplicate emails are prevented by the one-open-incident-per-feature rule, not this flag. |
| status | open / resolved. |
backend/app/scheduler.py) closes an open incident once the error rate drops back below the threshold, and sends a recovery email when recovery_enabled is on. Auto-resolve needs success traffic in the window — silence alone never closes an incident, so an error-only feature stays open until you click Resolve. You can always resolve manually (PATCH /api/monitor/incidents/{id} with status='resolved').reopen_count increments, reopened_at is set, and it shows a ↻ badge. Resolving manually never blocks a future reopen.In-app notifications
The bell icon in the dashboard reads from monitor_notifications. Each new incident inserts a row; clicking the notification or marking it read clears the badge. Notifications are per-user and don't replace email — they're a quick visual cue while you're already in the dashboard.
Webhooks
For Slack / Discord / PagerDuty fan-out, the natural integration point is the Webhooks product.
incident.opened / incident.resolved / incident.reopened, emitted from scheduler.py). The outbound Webhooks product (Slack / Discord / PagerDuty fan-out) does not yet emit these — backend/app/webhooks.py ships auth, waitlist, feedback and payment events only. Until incident webhooks land, bridge incident emails into chat.