notifications
5 endpoints.
/api/notifications/certification-requirements Bearer tokenSend a certification requirements notification email
Builds a digest of certification requirements grouped into due windows (overdue, next 10/30/60 days), plus the training events upcoming in the same horizon, renders it to HTML + plaintext, validates it against the @schemavaults/send-email request schema, and submits it to the configured transactional mail server. `audience: trainee` covers the user's own requirements; `audience: manager` covers the trainees holding roles the user manages. Superusers only — this is the manual trigger behind the admin notifications page; scheduled sending will reuse the same pipeline. Use `dry_run` to validate without sending.
| Field | Type | Required | Description |
|---|---|---|---|
| user | string (uuid) | yes | `users.id` of the user the digest is about — and, unless `email` overrides it, the recipient (resolved from their account's profile). |
| audience | string | yes | Whose deadlines the digest covers: `trainee` — the user's own certification requirements; `manager` — the requirements of every trainee holding a role the user manages (scoped to those managed roles). one of "trainee" | "manager" |
| string (email) | no | Send to this address instead of the user's account email. | |
| dry_run | boolean | no | Build, render and validate the email without contacting the mail server. Defaults to false. |
| include_empty | boolean | no | Send an "all clear" email even when no requirement is overdue or due within 60 days and no training event is upcoming. Defaults to false (such digests are skipped). |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The digest was sent, validated (dry_run), or skipped as empty. | CertificationNotificationResult |
| 400 | The request body failed validation. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The caller is not a superuser. | ErrorResponse |
| 404 | No recipient email address could be resolved for the user. | ErrorResponse |
| 500 | The digest could not be built or rendered. | ErrorResponse |
| 502 | The mail server rejected the message or could not be reached. | ErrorResponse |
/api/notifications/schedule Bearer tokenRead the notification schedule
When the scheduled certification requirement digests go out — every day or one weekday, at an hour of a timezone — with the next occurrence, the last one claimed, and the latest runs. Superusers only.
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The current schedule. | NotificationSchedule |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The caller is not a superuser. | ErrorResponse |
| 500 | The schedule could not be read. | ErrorResponse |
/api/notifications/schedule Bearer tokenChange the notification schedule
Sets any of: whether scheduled sending is on, the cadence (`daily` or `weekly`), the weekday, the hour and the IANA timezone. An omitted field is left as it is. The hourly cron tick reads the schedule from the database, so a change takes effect within the hour. Enabling a schedule whose latest occurrence has passed does not send retroactively: the first run is the next occurrence. Superusers only.
| Field | Type | Required | Description |
|---|---|---|---|
| enabled | boolean | no | — |
| cadence | string | no | `daily`: every day at the hour. `weekly`: on `weekday` only. one of "daily" | "weekly" |
| weekday | integer | no | Day of the week, 0 = Sunday … 6 = Saturday; used when weekly. minimum 0 · maximum 6 |
| hour | integer | no | Hour of the day, 0–23, on the wall clock of `timezone`. minimum 0 · maximum 23 |
| timezone | string | no | IANA timezone the hour is read in. 1–64 characters |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The updated schedule. | NotificationSchedule |
| 400 | The request body failed validation. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The caller is not a superuser. | ErrorResponse |
| 500 | The schedule could not be written to. | ErrorResponse |
/api/notifications/schedule/tick Bearer tokenTick the notification schedule (cron)
Called hourly by the deployment's cron (`vercel.json`). Resumes a run a previous tick left unfinished; otherwise, when the schedule is enabled and its latest occurrence has not been claimed yet, claims it and sends the digests to every trainee with a role and every manager, within one invocation's time budget. Authenticated by the deployment's `CRON_SECRET` as a bearer token, not by an OIDC access token; a deployment without the variable answers 401 to everyone.
Responses
| Status | Description | Body |
|---|---|---|
| 200 | What the tick did. | NotificationTickResult |
| 401 | The cron secret is missing or wrong. | ErrorResponse |
| 500 | The tick failed part-way; the next tick resumes the run. | ErrorResponse |
/api/notifications/schedule/run Bearer tokenSend the scheduled digests now
Starts a batch outside the schedule — the same digests to the same population the cron tick would send, whether or not scheduled sending is on — and processes as much of it as one invocation's time budget allows; a larger population is finished by the hourly tick. An unfinished earlier run is resumed instead of starting another, and while another invocation is still working on one the answer is `busy` with nothing started. Superusers only.
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The run, with how far it got. | NotificationTickResult |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The caller is not a superuser. | ErrorResponse |
| 500 | The run failed part-way; the next tick resumes it. | ErrorResponse |