GenerateSaaS

Admin Notifications

Push operator alerts for signups, purchases, refunds, and cancellations to Discord, Slack, or Telegram with @repo/admin-notifications.

@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 through a throttled background worker; when off, notifyAdmins() is a no-op and the worker is not registered.

This is not the in-app bell. Admin notifications go to you, out-of-band, in chat. For user-facing alerts see Notifications.

Configuration

config.adminNotifications is a discriminated union: { enabled: false } or { enabled: true; events? }.

KeyTypeDefaultDescription
enabledbooleantrueMaster flag; false makes notifyAdmins() a no-op and unregisters the worker.
eventsPartial<Record<Event, boolean>>?all onSet an event to false to suppress it; omitted events stay on.
packages/config/src/index.ts
adminNotifications: {
  enabled: true,
  events: { account_deleted: false } // suppress one event; the rest stay on
}

Credentials live in environment variables, not in config.

ProviderEnv vars
DiscordDISCORD_WEBHOOK_URL
SlackSLACK_WEBHOOK_URL
TelegramTELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID, optional TELEGRAM_TOPIC_ID

A provider activates only when its env vars are set. enabled: true with no credentials sends nothing - getAdminNotificationProviders() returns empty and the worker skips. See Environment Variables.

Events

These ten events trigger an alert, all on by default. They fire automatically from auth and payment webhooks - you do not wire them.

EventFires when
user_signupA new user registers
subscription_purchaseA subscription starts
lifetime_purchaseA lifetime plan is bought
plan_upgrade / plan_downgradeA plan changes tier
subscription_cancelA subscription is cancelled
refundA refund is processed
product_purchaseA one-time product is bought
credits_purchaseCredits are bought (custom or auto_topup)
account_deletedA 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) - master flag, at least one configured provider, 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) that the worker resolves to email and name at send time. refund and account_deleted carry email and 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 - one failing webhook never blocks the others.
  • It is registered in packages/api/src/routes/inngest.ts only 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? }>).

Create a class in packages/admin-notifications/src/providers/ implementing the interface.
Register its credential env vars in packages/runtime/src/env.ts - providers read from env, not config.
Push an instance in getAdminNotificationProviders() (service.ts), guarded by the presence of those env vars.

On this page