notifications

5 endpoints.

POST/api/notifications/certification-requirements Bearer token

Send 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.

Request body

application/jsonrequiredSendCertificationNotificationRequest
FieldTypeRequiredDescription
userstring (uuid)yes

`users.id` of the user the digest is about — and, unless `email` overrides it, the recipient (resolved from their account's profile).

audiencestringyes

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"

emailstring (email)no

Send to this address instead of the user's account email.

dry_runbooleanno

Build, render and validate the email without contacting the mail server. Defaults to false.

include_emptybooleanno

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

StatusDescriptionBody
200The digest was sent, validated (dry_run), or skipped as empty.CertificationNotificationResult
400The request body failed validation.ErrorResponse
401The access token is missing or invalid.ErrorResponse
403The caller is not a superuser.ErrorResponse
404No recipient email address could be resolved for the user.ErrorResponse
500The digest could not be built or rendered.ErrorResponse
502The mail server rejected the message or could not be reached.ErrorResponse
GET/api/notifications/schedule Bearer token

Read 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

StatusDescriptionBody
200The current schedule.NotificationSchedule
401The access token is missing or invalid.ErrorResponse
403The caller is not a superuser.ErrorResponse
500The schedule could not be read.ErrorResponse
PATCH/api/notifications/schedule Bearer token

Change 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.

Request body

application/jsonrequiredUpdateNotificationScheduleRequest
FieldTypeRequiredDescription
enabledbooleanno—
cadencestringno

`daily`: every day at the hour. `weekly`: on `weekday` only.

one of "daily" | "weekly"

weekdayintegerno

Day of the week, 0 = Sunday … 6 = Saturday; used when weekly.

minimum 0 · maximum 6

hourintegerno

Hour of the day, 0–23, on the wall clock of `timezone`.

minimum 0 · maximum 23

timezonestringno

IANA timezone the hour is read in.

1–64 characters

Responses

StatusDescriptionBody
200The updated schedule.NotificationSchedule
400The request body failed validation.ErrorResponse
401The access token is missing or invalid.ErrorResponse
403The caller is not a superuser.ErrorResponse
500The schedule could not be written to.ErrorResponse
GET/api/notifications/schedule/tick Bearer token

Tick 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

StatusDescriptionBody
200What the tick did.NotificationTickResult
401The cron secret is missing or wrong.ErrorResponse
500The tick failed part-way; the next tick resumes the run.ErrorResponse
POST/api/notifications/schedule/run Bearer token

Send 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

StatusDescriptionBody
200The run, with how far it got.NotificationTickResult
401The access token is missing or invalid.ErrorResponse
403The caller is not a superuser.ErrorResponse
500The run failed part-way; the next tick resumes it.ErrorResponse