.agents/features/alerts.md
Flow Failure Alerts allow users to subscribe to email notifications when a flow run fails. When a flow fails for the first time within a 24-hour window, the system sends an email to all configured alert receivers for that project. Subsequent failures of the same flow version within the same window are suppressed using a Redis counter to avoid alert spam. Both personal and team projects support alerts: personal projects allow exactly one receiver — the project owner — toggled via a single switch, while team projects allow any number of receivers managed by project admins. Platform admins can also bulk subscribe/unsubscribe their own email across many projects from the platform admin Projects table. This feature is available on Cloud and Enterprise editions only.
packages/server/api/src/app/ee/alerts/alerts-controller.ts — REST controller (list, create, delete)packages/server/api/src/app/ee/alerts/alerts-service.ts — alert dispatch logic, Redis deduplication, case-insensitive receiver storage, personal-project owner-only constraintpackages/server/api/src/app/ee/helper/email/email-service.ts — sendIssueCreatedNotification (IssueCreatedArgs shape: projectName, runUrl, failedStepDisplayName, failedStepNumber, optional failedStepMessage)packages/server/api/src/app/ee/helper/email/email-sender/smtp-email-sender.ts — getEmailSubject builds the inbox subject linepackages/server/api/src/assets/emails/issue-created.html — Mustache template for the failure emailpackages/server/api/src/app/ee/alerts/alerts-entity.ts — TypeORM entitypackages/server/api/src/app/ee/alerts/alerts-module.ts — module registration (no project-type restriction)packages/server/api/src/app/ee/projects/ee-project-hooks.ts — auto-subscribes the owner on personal-project creation; auto-subscribes alertReceiverEmail on team-project creationpackages/server/api/src/app/project/project-hooks.ts — ProjectHooks.postCreate accepts ProjectPostCreateContextpackages/server/api/src/app/project/project-service.ts — plumbs postCreateContext through to hookspackages/server/api/src/app/ee/projects/platform-project-controller.ts — accepts alertReceiverEmail on the create-project requestpackages/server/api/src/app/ee/projects/platform-project-service.ts — forwards alertReceiverEmail into callProjectPostCreateHookspackages/shared/src/lib/ee/alerts/alerts-dto.ts — Alert type and AlertChannel enumpackages/shared/src/lib/ee/alerts/alerts-requests.ts — ListAlertsParams and CreateAlertParams Zod schemaspackages/shared/src/lib/management/project/project-requests.ts — CreatePlatformProjectRequest.alertReceiverEmailpackages/web/src/features/alerts/api/alerts-api.ts — frontend API clientpackages/web/src/features/alerts/hooks/alert-hooks.ts — React Query hooks: useAlertsEmailList, useCreateAlert, useDeleteAlert, useBulkSubscribeAlerts, useBulkUnsubscribeAlertspackages/web/src/app/components/project-settings/alerts/index.tsx — routes between personal/team UI based on currentProject.typepackages/web/src/app/components/project-settings/alerts/personal-project-alerts.tsx — single-switch UI for personal projectspackages/web/src/app/components/project-settings/alerts/team-project-alerts.tsx — receiver list UI for team projectspackages/web/src/app/components/project-settings/alerts/add-alert-email-form.tsx — inline add-receiver form (replaces the prior dialog)packages/web/src/features/projects/components/platform-admin-project-alert-subscription-bulk-actions.tsx — bulk subscribe/unsubscribe action on the platform admin Projects tablepackages/web/src/features/projects/components/new-project-dialog.tsx — exposes the Alert Receiver Email field on project creationCloud (AP_EDITION=cloud) and Enterprise (AP_EDITION=ee). The service checks paidEditions at runtime when dispatching alert emails. Both personal and team projects can subscribe, with different rules:
alertsService.add() and surfaced in the UI as a single on/off switch.No specific plan flag gates this feature — the edition check is in the service logic.
EMAIL.LOWER(receiver)).flow_fail_count:<flowVersionId> tracks failure count per 24-hour window; only the first failure triggers an email.p-limit.Table name: alert
| Column | Type | Notes |
|---|---|---|
| id | ApId (string) | PK |
| created | string | From BaseColumnSchemaPart |
| updated | string | From BaseColumnSchemaPart |
| projectId | ApId | FK to project |
| channel | string | AlertChannel enum value |
| receiver | string | Email address (stored lowercased) |
Constraints: no unique index, but the service enforces one alert per (projectId, LOWER(receiver)) pair at the application level.
All endpoints mount under /v1/alerts.
| Method | Path | Auth | Permission | Description |
|---|---|---|---|---|
| GET | /v1/alerts | USER | READ_ALERT | List alerts for a project (paginated) |
| POST | /v1/alerts | USER | WRITE_ALERT | Add an alert receiver |
| DELETE | /v1/alerts/:id | USER | WRITE_ALERT | Remove an alert by ID |
Query for list: { projectId, cursor?, limit? }.
Body for create: { projectId, channel, receiver }.
sendAlertOnRunFinish({ issueToAlert, flowRunId, failedStep }) — called after a flow run finishes. failedStep: FailedStep is required; flow-run-hooks.onFinish already gates the call on !isNil(flowRun.failedStep) for failed production runs, so the contract reflects reality. Increments the Redis counter; on the first failure in 24 hours, fetches the run's own flow version via flowVersionService.getOneOrThrow(issueToAlert.flowVersionId) (not the latest locked version — using the run's own version guarantees failedStep.name is findable in the structure, so failedStepNumber is always present) and sends the issue-created email to every receiver for the project.add({ projectId, channel, receiver }) — creates a new alert. Lowercases the receiver. For personal projects, throws VALIDATION if the receiver is not the owner's identity email. Throws EXISTING_ALERT_CHANNEL (case-insensitive lookup) if the receiver already exists for the project.list({ projectId, cursor, limit }) — returns a paginated SeekPage<Alert>.delete({ alertId }) — removes an alert record.Template: packages/server/api/src/assets/emails/issue-created.html. Mustache vars: projectName, flowName, createdAt, failedStepNumber, failedStepDisplayName, failedStepMessage (optional), runUrl. Layout: heading → "First seen at … To recover, retry this run from the runs table, or update the flow, republish, and retry it again." → labeled rows for Project, Failed at, optional Reason → CTA "View Run" linking to projects/<projectId>/runs/<flowRunId>.
Subject (built in smtp-email-sender.ts:getEmailSubject): `[${projectName}] Flow has an issue "${flowName}" ⚠️`.
There is no Issues feature in the product; the email points directly at the run page. The previously dead checkIssuesEnabled / isIssue plumbing and "View Issue" CTA were removed in this branch.
The CE ProjectHooks.postCreate signature accepts a ProjectPostCreateContext:
type ProjectPostCreateContext = {
alertReceiverEmail?: string | null
}
The EE override (projectEnterpriseHooks) consumes it as follows:
alertReceiverEmail provided in the create-project request, if any. Pre-existing-receiver conflicts (EXISTING_ALERT_CHANNEL) are swallowed; other errors propagate.CreatePlatformProjectRequest.alertReceiverEmail is the inbound field. The platform project controller plumbs it into platformProjectService.create, which forwards it as postCreateContext to projectService.callProjectPostCreateHooks.
PersonalProjectAlerts or TeamProjectAlerts based on currentProject.type. The tab is enabled for both project types (previously gated on ProjectType.TEAM).useCreateAlert / useDeleteAlert.WRITE_ALERT + WRITE_PROJECT) can delete receivers.Alert Receiver Email field that is sent on creation for team projects.The alert list query key is per-project: ['alerts-email-list', projectId] as const. useCreateAlert and useDeleteAlert invalidate the current project's key. Bulk mutations operate across many projects and do not invalidate (the platform admin table refreshes on its own selection reset).
Key: flow_fail_count:<flowVersionId>, incremented on each failure and expires after 86 400 seconds (1 day). If the count after increment is greater than 1, the email is not sent.