- WhatsApp, in the
publicschema. - Trainee push notifications, in the
publicschema. - The coach web app’s notification bell, in the
authschema.
WhatsappTemplate
Table whatsapp_templates. A message template the studio wrote in Perform.
These are Perform-side text templates. Approved WhatsApp Business templates live in the studio’s SmartSend account and are listed live through the SmartSend API. They are not stored here.
WhatsappMessage
Table whatsapp_messages. A log of messages sent and received.
Indexes:
studioId, clientId, (clientId, createdAt), (studioId, createdAt).
MessageStatus
An inbound message from a known trainee also creates an
InboxItem of type MESSAGE with refId pointing at the message row.
Trainee push
TraineePushToken
Table trainee_push_tokens. One row per device.
Unique on
(clientId, token).
Lifecycle:
- Registered by
POST /v1/trainee/push-token, removed byDELETE /v1/trainee/push-token. - Pruned by
prunePushTokenswhen Expo reportsDeviceNotRegistered, on a ticket or on a receipt. - Deleted explicitly when a trainee deletes their account, because that is a soft delete and the cascade never fires.
NotificationConfigType
Fifteen values. Twelve are active (ACTIVE_NOTIFICATION_TYPES in notification-configs.schema.ts).
The three workout reminder types are scheduled on the device by the app. The server only supplies their text.
NotificationTypeConfig
Table notification_type_configs. The global default for each type. Managed through /v1/admin/notification-configs with service auth.
StudioNotificationConfig
Table studio_notification_configs. A studio’s override of one type. Same columns as the global row plus studioId.
Unique on (studioId, type). Index on studioId.
resolveNotificationConfig in apps/core-api/src/lib/trainee-push.ts picks the studio row, then the global row, then the code defaults in NOTIFICATION_CONFIG_DEFAULTS.
messages
Most types are single: one title and body, taken from titleTemplate and bodyTemplate. Two types hold several messages in the messages column, validated by notificationMessage:
Limits from
NOTIFICATION_CATALOGUE: up to 3 messages for FORM_SENT (MAX_FORM_SENT_MESSAGES) and up to 10 for FORM_REMINDERS (MAX_FORM_REMINDERS). The replace endpoint accepts 1 to 20.
When the stored messages value does not parse, messagesOf falls back to the code defaults for that type.
Trainee mute groups
Client.mutedNotificationGroups holds the groups a trainee switched off in the app: CHECK_INS, FORMS, PLANS, WORKOUT_REMINDERS, COACH_MESSAGES, SUBSCRIPTION.
isNotificationMuted(type, mutedGroups, formType) maps a type to its group with notificationGroupFor. COACH_MESSAGES is in LOCKED_NOTIFICATION_GROUPS and can never be muted, whatever the array says.
A muted recipient is reported in the muted list of the send result. It is not counted as a failure.
Sending, tickets and receipts are covered in Push notifications.
Coach web notifications
These live in theauth schema and are declared only in packages/database. The code is in packages/notifications.
Notification
Table auth.notification.
UserNotificationPreference
Table auth.user_notification_preference. A row means the user turned that type off for that target.
Unique on
(userId, type, target).
createNotification in packages/notifications/src/create-notification.ts checks isNotificationDisabled for each target. It inserts the in-app row unless in-app is disabled, and sends the notification email template unless email is disabled.
The settings page groups come from NOTIFICATION_GROUPS in packages/notifications/src/catalog.ts. Today there is one group, general, containing APP_UPDATE.
The bell in the web app’s navigation bar reads these rows through the oRPC procedures notifications.unreadCount and notifications.list. The queries are in packages/database/prisma/queries/notifications.ts.
Coach tasks on the Work Board are a different thing. They are
InboxItem rows in the public schema. See Automation and tasks.