Admin Notifications
Push real-time operator alerts for signups, purchases, refunds, and cancellations to Discord, Slack, or Telegram with the @repo/admin-notifications package.
@repo/admin-notifications pushes real-time operator alerts to your team chat, gated by config.adminNotifications.enabled in packages/config/src/index.ts. When enabled (the shipped default), business events fan out to every configured chat provider via a throttled background worker; when off, notifyAdmins() is a no-op and the worker is not registered.
This is not the in-app bell. User-facing notifications are a separate channel - see Notifications. Admin notifications go to you (the operator), out-of-band, in chat.
What you get
- A fire-and-forget
notifyAdmins({ event, data })helper - enqueues, never blocks the request. - A throttled, retrying worker that fans out to every configured provider at once.
- Three chat providers: Discord, Slack, Telegram - auto-enabled by env.
- Per-event opt-out via
config.adminNotifications.events.
Configuration
config.adminNotifications is a discriminated union: { enabled: false } or { enabled: true; events? }.
| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Master flag; false makes notifyAdmins() a no-op and unregisters the worker. |
events | Partial<Record<Event, boolean>>? | all on | Set an event to false to suppress it. |
adminNotifications: {
enabled: true,
events: { account_deleted: false } // suppress one event; the rest stay on
}Credentials live in environment variables, not in config. A provider activates only when its env vars are set - enabled: true with no env vars sends nothing.
| Provider | Env vars |
|---|---|
| Discord | DISCORD_WEBHOOK_URL |
| Slack | SLACK_WEBHOOK_URL |
| Telegram | TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID, optional TELEGRAM_TOPIC_ID |
Events
These ten events trigger an alert (all on by default). They fire automatically from auth and payment webhooks - you do not wire them.
| Event | Fires when |
|---|---|
user_signup | A new user registers |
subscription_purchase | A subscription starts |
lifetime_purchase | A lifetime plan is bought |
plan_upgrade / plan_downgrade | A plan changes tier |
subscription_cancel | A subscription is cancelled |
refund | A refund is processed |
product_purchase | A one-time product is bought |
credits_purchase | Credits are bought (custom or auto_topup) |
account_deleted | A user deletes their account |
Sending an alert
notifyAdmins(notification) (packages/admin-notifications/src/service.ts) is the only call you make. It short-circuits via isAdminNotificationEnabled(event) - checking the master flag, at least one configured provider, and the per-event opt-out - then enqueues an admin-notification/send event. It never reaches a provider inline.
import { notifyAdmins } from "@repo/admin-notifications";
await notifyAdmins({
event: "subscription_purchase",
data: { entityType: "user", entityId, plan: "pro", interval: "month", amount: 20, currency: "USD" }
});Most events carry an EntityRef (entityType + entityId); the worker resolves it to email/name at send time. refund and account_deleted carry email/name directly - by the time they run, the user row may be gone.
Delivery
The worker adminNotificationFunction (packages/api/src/functions/notifications/admin-notification.ts) is throttled to 10 events/second and retries up to 3 times. It resolves the entity, runs formatNotificationPayload() once into a provider-agnostic shape (title, message, color, fields), then sends to all providers with Promise.allSettled so one failing webhook never blocks the others.
It is only registered in packages/api/src/routes/inngest.ts when config.adminNotifications.enabled is true. See Background jobs.
Adding a provider
Each provider implements AdminNotificationProviderInterface from packages/admin-notifications/src/types.ts (send(payload) => Promise<{ success; messageId? }>).
packages/admin-notifications/src/providers/ implementing the interface.packages/runtime/src/env.ts (providers read from env, not config).getAdminNotificationProviders() (service.ts), guarded by the presence of those env vars.Frequently asked questions
I enabled it but get no alerts - why?
A provider activates only when its env vars are set. With enabled: true and no DISCORD_WEBHOOK_URL / SLACK_WEBHOOK_URL / Telegram vars, getAdminNotificationProviders() returns empty and the worker skips.
How do I silence one event?
Set it false in config.adminNotifications.events (e.g. { user_signup: false }). Omitted events stay on.
Do alerts go to users? No. This channel is operator-only chat. For the user-facing bell and broadcasts, see Notifications.
Why didn't notifyAdmins() deliver instantly?
It enqueues an admin-notification/send event; the throttled worker delivers it, retrying up to 3 times.