Sending Notifications

Create in-app notifications from Server Actions, API routes, and background jobs using the notifications API.

Notifications are created server-side using createNotificationsApi. This requires the Supabase admin client because only the service_role can insert into the notifications table.

Basic usage

import { createNotificationsApi } from '@kit/notifications/api';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
async function sendNotification(accountId: string) {
const client = getSupabaseServerAdminClient();
const api = createNotificationsApi(client);
await api.createNotification({
account_id: accountId,
body: 'Your report is ready',
});
}

The account_id determines who sees the notification. Pass a user's personal account ID to notify just them, or a team account ID to notify all team members.

Notification fields

FieldTypeRequiredDefaultDescription
account_iduuidYes-Personal or team account ID
bodystringYes-Message text (max 5000 chars)
type'info' | 'warning' | 'error'No'info'Severity level
linkstringNonullURL to navigate on click
channel'in_app' | 'email'No'in_app'Delivery channel
expires_atDateNo1 monthAuto-expiration timestamp

Notification types

Use types to indicate severity. Each type renders with a distinct icon color:

// Info (blue) - General updates
await api.createNotification({
account_id: accountId,
body: 'New feature: Dark mode is now available',
type: 'info',
});
// Warning (yellow) - Attention needed
await api.createNotification({
account_id: accountId,
body: 'Your trial expires in 3 days',
type: 'warning',
link: '/settings/billing',
});
// Error (red) - Action required
await api.createNotification({
account_id: accountId,
body: 'Payment failed. Update your card to continue.',
type: 'error',
link: '/settings/billing',
});

Include a link to make notifications actionable. Users click the notification to navigate:

await api.createNotification({
account_id: accountId,
body: 'John commented on your document',
link: '/documents/abc123#comment-456',
});

Links should be relative paths within your app. The UI renders the body as a clickable anchor.

Setting expiration

By default, notifications expire after 1 month. Set a custom expiration for time-sensitive messages:

// Expire in 24 hours
const tomorrow = new Date();
tomorrow.setHours(tomorrow.getHours() + 24);
await api.createNotification({
account_id: accountId,
body: 'Flash sale ends tonight!',
link: '/pricing',
expires_at: tomorrow,
});

Expired notifications are filtered out on fetch. They remain in the database but won't appear in the UI.

Team notifications

Send to a team account ID to notify all members:

import { createNotificationsApi } from '@kit/notifications/api';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
async function notifyTeam(teamAccountId: string, newMemberName: string) {
const client = getSupabaseServerAdminClient();
const api = createNotificationsApi(client);
await api.createNotification({
account_id: teamAccountId,
body: `${newMemberName} joined the team`,
link: '/settings/members',
type: 'info',
});
}

Every user with a role on that team account will see this notification via the RLS policy.

Common patterns

Welcome notification on signup

// In your post-signup hook or Server Action
export async function onUserCreated(userId: string) {
const client = getSupabaseServerAdminClient();
const api = createNotificationsApi(client);
await api.createNotification({
account_id: userId,
body: 'Welcome! Start by creating your first project.',
link: '/projects/new',
type: 'info',
});
}

Subscription renewal reminder

export async function sendRenewalReminder(
accountId: string,
daysRemaining: number
) {
const client = getSupabaseServerAdminClient();
const api = createNotificationsApi(client);
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + daysRemaining);
await api.createNotification({
account_id: accountId,
body: `Your subscription renews in ${daysRemaining} days`,
link: '/settings/billing',
type: daysRemaining <= 3 ? 'warning' : 'info',
expires_at: expiresAt,
});
}

Background job completion

export async function onExportComplete(
accountId: string,
exportId: string
) {
const client = getSupabaseServerAdminClient();
const api = createNotificationsApi(client);
await api.createNotification({
account_id: accountId,
body: 'Your data export is ready to download',
link: `/exports/${exportId}`,
type: 'info',
});
}

Payment failure

export async function onPaymentFailed(accountId: string) {
const client = getSupabaseServerAdminClient();
const api = createNotificationsApi(client);
await api.createNotification({
account_id: accountId,
body: 'Payment failed. Please update your payment method.',
link: '/settings/billing',
type: 'error',
});
}

Using translation keys

For internationalized apps, store translation keys instead of plain text:

await api.createNotification({
account_id: accountId,
body: 'notifications:exportReady', // Translation key
link: '/exports',
});

The UI component runs the body through t() from react-i18next, falling back to the raw string if no translation exists.

Add the translation to your locale files:

{
"notifications": {
"exportReady": "Your data export is ready to download"
}
}

Notification channels

The channel field supports 'in_app' (default) and 'email'. Currently, only in_app is implemented. The email channel is reserved for future use where a database trigger could send email notifications.

// In-app only (default)
await api.createNotification({
account_id: accountId,
body: 'New message received',
channel: 'in_app',
});

Error handling

The API throws on failure. Wrap calls in try-catch for production code:

try {
await api.createNotification({
account_id: accountId,
body: 'Notification message',
});
} catch (error) {
console.error('Failed to send notification:', error);
// Don't throw - notification failure shouldn't break the main flow
}

Notifications are typically non-critical. Consider logging failures but not throwing, so the primary operation (signup, export, etc.) still succeeds.