Feature: Named project alerts instead of instance-wide fan-out
Related to
Web-Frontend (what users interact with), Web-Backend (APIs), Service (scheduled tasks, alerts), Database
Impact
major improvement to user experience
Missing Feature
Alerting today is instance-wide: one Slack URL, one Teams URL, one Telegram chat in config.json, plus a project alert toggle and per-template suppress flags. Every job that is allowed to alert fans out to the same destinations. A new job that nobody configured still notifies everyone. There is no way to send “Ops Telegram” from one job and “Slack prod” from another, or to keep a nightly cron quiet while the same job still alerts when run by hand.
Related: #3387, #3588, #3724, https://github.com/semaphoreui/semaphore/discussions/2940. Maintainer note on #3588: notification refactor is on the roadmap.
Implementation
Treat an alert as one object: channel + destination + message. Users pick those objects on the job. Empty selection means no send. There is no runtime global default.
Alerting page (sidebar)
- Create named entries: type (Telegram, Slack, email, Teams, Rocket.Chat, DingTalk, Gotify), destination (chat/thread, webhook URL, recipients), optional message body, enabled flag
- Clone an entry to reuse a channel with a different text
- Test one alert or all enabled alerts
- No
is_default. Instanceconfig.jsonstays infrastructure only (SMTP, bot token)
Job (template)
- Multiselect of alert entries + on success / on error
- Message text is not chosen again on the job; it lives on the entry
- Empty list → no alerts (manual and, if the schedule inherits, cron)
Schedule
Inherit from job(use the job list, including empty) or an own list- Own empty list → this cron is silent; manual runs still follow the job
- Optional success/error overrides only when the schedule has its own list
Task
└─ schedule has its own list? ──yes──► schedule list (may be empty)
│
no (inherit)
▼
job list (may be empty)
│
empty? ──yes──► no send
│
no
▼
send each selected entryExamples:
- Job with no alerts selected → nothing is sent (manual or inheriting cron)
- Job selects “Ops Telegram” + “Slack prod” → both fire, each with its own body
- Schedule overrides with “Nightly Telegram” → only that cron differs
- Schedule own list empty → cron silent, manual still uses the job
Design
alert:id,project_id,name,type,enabled, destination fields,body. Unique(project_id, name). Delete is blocked while templates or schedules still reference it.- Job:
alert_ids[](empty = nothing),alert_on_success,alert_on_error. A PUT that omitsalert_idsleaves bindings unchanged;[]clears them (same as variable groups). - Schedule:
alert_modeinherit|ids; inidsmodealert_ids[]may be empty;alert_on_*nullable = inherit from the job. - Task snapshot: resolved IDs + events at task creation so every HA node sends the same set. Dedup via
task__alert_sendunique(task_id, alert_id, event). - Migration (one-shot, not a runtime default): turn instance/project channels into alert rows and attach those IDs to templates that already alerted (
project.alertand not both suppress flags). Schedules stayinherit. After that, only the job list matters. - Security: webhook URLs must be
http/https; loopback, link-local, and cloud metadata hosts are rejected; Gotify token is write-only in API JSON; webhook client does not follow redirects. - v1 scope: success/error only (no
waiting_confirmation). Events apply to the whole selected set. Useralertstays the email opt-in for “project users”. - Not v1: new providers, error-regex routing, recovered/duration events, integration bindings, extra-vars in the body.
Source: semaphoreui/semaphore