Learn how to send notifications to accounts and workspaces using the Asterisms JS SDK Backend.
The notification service allows you to send notifications to individual accounts or entire workspaces. Notifications can be used for alerts, updates, messages, and other real-time communication.
const notifications = sdk.notifications();import { createNotification } from '@asterisms/sdk-backend';
try {
const notification = createNotification({
sender: 'your-app',
subject: 'Welcome!',
text: 'Thanks for joining our service',
urgency: 'NONE'
});
await notifications.sendToAccount(accountId, notification);
console.log('Notification sent successfully');
} catch (error) {
console.error('Failed to send notification:', error);
}try {
const notification = createNotification({
sender: 'your-app',
subject: 'New Update Available',
text: 'Version 2.0 has been released with exciting new features',
html: '<p>Version <strong>2.0</strong> has been released with exciting new features</p>',
urgency: 'IMPORTANT'
});
await notifications.sendToWorkspace(workspaceId, notification);
console.log('Workspace notification sent');
} catch (error) {
console.error('Failed to send workspace notification:', error);
}import {
createNotification,
type Notification,
type NotificationDeliveryDirective,
type NotificationDeliverySchedule
} from '@asterisms/sdk-backend';
interface CreateNotificationOptions {
sender: string;
subject: string;
text: string;
html?: string;
urgency?: 'NONE' | 'IMPORTANT' | 'ERROR' | 'CRITICAL';
account?: NotifiableAccount | null;
directive?: NotificationDeliveryDirective;
delivery?: NotificationDelivery;
subscriptionKey?: string | null;
}// Basic information notification
const infoNotification = createNotification({
sender: 'your-app',
subject: 'Profile Updated',
text: 'Your profile has been successfully updated',
urgency: 'NONE'
});
await notifications.sendToAccount(accountId, infoNotification);
// Important notification
const importantNotification = createNotification({
sender: 'your-app',
subject: 'Task Completed',
text: 'Your task has been completed successfully',
html: '<p>Your task <strong>has been completed</strong> successfully</p>',
urgency: 'IMPORTANT'
});
await notifications.sendToAccount(accountId, importantNotification);
// High priority warning
const warningNotification = createNotification({
sender: 'your-app',
subject: 'Storage Almost Full',
text: 'Your storage is 90% full. Please free up space.',
urgency: 'ERROR',
directive: 'ALL' // Send to all notification channels
});
await notifications.sendToAccount(accountId, warningNotification);
// Critical alert
const criticalNotification = createNotification({
sender: 'your-app',
subject: 'Security Alert',
text: 'Unusual activity detected on your account',
urgency: 'CRITICAL',
directive: 'ALL',
delivery: {
schedule: 'IMMEDIATE',
delay: 'PT0S' // ISO-8601 duration: immediate
}
});
await notifications.sendToAccount(accountId, criticalNotification);// src/routes/api/notifications/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { createNotification } from '@asterisms/sdk-backend/notifications';
export const POST: RequestHandler = async ({ request, locals }) => {
try {
const sdk = locals.sdk;
if (!sdk) {
return json({ error: 'SDK not available' }, { status: 503 });
}
const { type, accountId, workspaceId, notificationData } = await request.json();
// Validate input
if (!notificationData?.sender || !notificationData?.subject || !notificationData?.text) {
return json({ error: 'Sender, subject, and text are required' }, { status: 400 });
}
// Check authentication
const auth = sdk.authorization();
const currentUser = await auth.getCurrentUser();
if (!currentUser) {
return json({ error: 'Authentication required' }, { status: 401 });
}
const notifications = sdk.notifications();
const notification = createNotification(notificationData);
if (type === 'account' && accountId) {
// Send to specific account
await notifications.sendToAccount(accountId, notification);
} else if (type === 'workspace' && workspaceId) {
// Check permission to send workspace notifications
const canSendToWorkspace = await auth.hasPermission('workspace.notify');
if (!canSendToWorkspace) {
return json({ error: 'Insufficient permissions' }, { status: 403 });
}
await notifications.sendToWorkspace(workspaceId, notification);
} else {
return json({ error: 'Invalid notification type or missing target' }, { status: 400 });
}
return json({ success: true });
} catch (error) {
console.error('Notification API error:', error);
return json({ error: 'Failed to send notification' }, { status: 500 });
}
};<!-- src/lib/components/NotificationSender.svelte -->
<script lang="ts">
import { createEventDispatcher } from 'svelte';
export let type: 'account' | 'workspace' = 'account';
export let targetId: string = '';
const dispatch = createEventDispatcher();
let sender = 'your-app';
let subject = '';
let text = '';
let html = '';
let urgency: 'NONE' | 'IMPORTANT' | 'ERROR' | 'CRITICAL' = 'NONE';
let sending = false;
async function sendNotification() {
if (!sender.trim() || !subject.trim() || !text.trim() || !targetId.trim()) {
alert('Please fill in sender, subject, text, and target ID');
return;
}
sending = true;
try {
const response = await fetch('/api/notifications', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
type,
[type === 'account' ? 'accountId' : 'workspaceId']: targetId,
notificationData: {
sender,
subject,
text,
html: html || undefined,
urgency
}
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
dispatch('success', result);
// Clear form
sender = 'your-app';
subject = '';
text = '';
html = '';
urgency = 'NONE';
} catch (error) {
console.error('Error sending notification:', error);
dispatch('error', error);
} finally {
sending = false;
}
}
</script>
<div class="notification-sender">
<h3>Send Notification</h3>
<div class="form-group">
<label for="type">Type:</label>
<select id="type" bind:value={type}>
<option value="account">Account</option>
<option value="workspace">Workspace</option>
</select>
</div>
<div class="form-group">
<label for="targetId">Target ID:</label>
<input
id="targetId"
type="text"
bind:value={targetId}
placeholder={type === 'account' ? 'Account ID' : 'Workspace ID'}
/>
</div>
<div class="form-group">
<label for="sender">Sender:</label>
<input
id="sender"
type="text"
bind:value={sender}
placeholder="App identifier"
/>
</div>
<div class="form-group">
<label for="subject">Subject:</label>
<input
id="subject"
type="text"
bind:value={subject}
placeholder="Notification subject"
/>
</div>
<div class="form-group">
<label for="text">Text:</label>
<textarea
id="text"
bind:value={text}
placeholder="Notification content"
rows="3"
></textarea>
</div>
<div class="form-group">
<label for="html">HTML (optional):</label>
<textarea
id="html"
bind:value={html}
placeholder="HTML content (optional)"
rows="3"
></textarea>
</div>
<div class="form-group">
<label for="urgency">Urgency:</label>
<select id="urgency" bind:value={urgency}>
<option value="NONE">None</option>
<option value="IMPORTANT">Important</option>
<option value="ERROR">Error</option>
<option value="CRITICAL">Critical</option>
</select>
</div>
<button
type="button"
on:click={sendNotification}
disabled={sending}
>
{sending ? 'Sending...' : 'Send Notification'}
</button>
</div>
<style>
.notification-sender {
max-width: 500px;
margin: 0 auto;
padding: 20px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input, textarea, select {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
background-color: #007cba;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
</style>
title,
message,
type: notificationType,
priority
}
})
});
if (response.ok) {
title = '';
message = '';
dispatch('sent', { type, targetId });
alert('Notification sent successfully!');
} else {
const error = await response.json();
alert(`Failed to send notification: ${error.error}`);
}
} catch (error) {
console.error('Send notification error:', error);
alert('Error sending notification');
} finally {
sending = false;
}
}
</script>
<div class="notification-sender">
<h3>Send {type === 'user' ? 'User' : 'Workspace'} Notification</h3>
<div class="form-group">
<label for="targetId">
{type === 'user' ? 'User ID' : 'Workspace ID'}:
</label>
<input
id="targetId"
type="text"
bind:value={targetId}
placeholder={type === 'user' ? 'Enter user ID' : 'Enter workspace ID'}
/>
</div>
<div class="form-group">
<label for="title">Title:</label>
<input
id="title"
type="text"
bind:value={title}
placeholder="Notification title"
/>
</div>
<div class="form-group">
<label for="message">Message:</label>
<textarea
id="message"
bind:value={message}
placeholder="Notification message"
rows="3"
></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label for="type">Type:</label>
<select id="type" bind:value={notificationType}>
<option value="info">Info</option>
<option value="success">Success</option>
<option value="warning">Warning</option>
<option value="error">Error</option>
</select>
</div>
<div class="form-group">
<label for="priority">Priority:</label>
<select id="priority" bind:value={priority}>
<option value="low">Low</option>
<option value="normal">Normal</option>
<option value="high">High</option>
<option value="urgent">Urgent</option>
</select>
</div>
</div>
<button
on:click={sendNotification}
disabled={sending || !title.trim() || !message.trim() || !targetId.trim()}
class="send-button"
>
{sending ? 'Sending...' : 'Send Notification'}
</button>
</div>
<style>
.notification-sender {
background: white;
padding: 20px;
border-radius: 8px;
border: 1px solid #ddd;
max-width: 500px;
}
.form-group {
margin-bottom: 15px;
}
.form-row {
display: flex;
gap: 15px;
}
.form-row .form-group {
flex: 1;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input, textarea, select {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.send-button {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.send-button:disabled {
background: #ccc;
cursor: not-allowed;
}
.send-button:hover:not(:disabled) {
background: #0056b3;
}
</style><!-- src/routes/notifications/+page.svelte -->
<script lang="ts">
import NotificationSender from '$lib/components/NotificationSender.svelte';
function onNotificationSent(event) {
console.log('Notification sent:', event.detail);
}
</script>
<div class="notifications-page">
<h1>Send Notifications</h1>
<div class="notification-forms">
<NotificationSender
type="user"
on:sent={onNotificationSent}
/>
<NotificationSender
type="workspace"
on:sent={onNotificationSent}
/>
</div>
</div>
<style>
.notifications-page {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.notification-forms {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
gap: 20px;
margin-top: 20px;
}
</style>// Send notifications to multiple accounts
async function sendBulkNotifications(
accountIds: string[],
notificationData: CreateNotificationOptions
) {
const notifications = sdk.notifications();
const results = [];
for (const accountId of accountIds) {
try {
const notification = createNotification(notificationData);
await notifications.sendToAccount(accountId, notification);
results.push({ accountId, success: true });
} catch (error) {
console.error(`Failed to send to ${accountId}:`, error);
results.push({ accountId, success: false, error: error.message });
}
}
return results;
}
// Usage
const accountIds = ['account1', 'account2', 'account3'];
const notificationData = {
sender: 'your-app',
subject: 'System Maintenance',
text: 'System will be down for maintenance from 2-4 AM',
urgency: 'IMPORTANT' as const
};
const results = await sendBulkNotifications(accountIds, notificationData);
console.log('Bulk notification results:', results);// Define notification templates
const templates = {
welcome: (userName: string) => ({
sender: 'your-app',
subject: 'Welcome to Asterisms!',
text: `Hello ${userName}, welcome to our platform!`,
urgency: 'NONE' as const
}),
taskCompleted: (taskName: string) => ({
sender: 'your-app',
subject: 'Task Completed',
text: `Your task "${taskName}" has been completed successfully`,
urgency: 'NONE' as const
}),
paymentFailed: (amount: number) => ({
sender: 'your-app',
subject: 'Payment Failed',
text: `Payment of $${amount} could not be processed`,
urgency: 'CRITICAL' as const
})
};
// Usage
const notifications = sdk.notifications();
await notifications.sendToAccount(accountId, createNotification(templates.welcome('John Doe')));
await notifications.sendToAccount(
accountId,
createNotification(templates.taskCompleted('Data Analysis'))
);// Example: Schedule a notification (implementation depends on your scheduler)
import { schedule } from 'node-cron';
function scheduleNotification(
cronExpression: string,
accountId: string,
notificationData: CreateNotificationOptions
) {
schedule(cronExpression, async () => {
try {
const notifications = sdk.notifications();
const notification = createNotification(notificationData);
await notifications.sendToAccount(accountId, notification);
console.log(`Scheduled notification sent to ${accountId}`);
} catch (error) {
console.error('Scheduled notification failed:', error);
}
});
}
// Schedule daily reminder at 9 AM
scheduleNotification('0 9 * * *', 'account123', {
sender: 'your-app',
subject: 'Daily Reminder',
text: "Don't forget to check your tasks today!",
urgency: 'NONE'
});import { AsterismsBackendError } from '@asterisms/sdk-backend';
try {
const notification = createNotification(notificationData);
await notifications.sendToAccount(accountId, notification);
} catch (error) {
if (error instanceof AsterismsBackendError) {
switch (error.code) {
case 'ACCOUNT_NOT_FOUND':
console.error('Account not found:', accountId);
break;
case 'NOTIFICATION_QUOTA_EXCEEDED':
console.error('Notification quota exceeded');
break;
case 'NOTIFICATION_INVALID':
console.error('Invalid notification data');
break;
default:
console.error('Notification error:', error.message);
}
} else {
console.error('Unknown notification error:', error);
}
}async function sendNotificationWithRetry(
accountId: string,
notificationData: CreateNotificationOptions,
maxRetries: number = 3
): Promise<boolean> {
const notifications = sdk.notifications();
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const notification = createNotification(notificationData);
await notifications.sendToAccount(accountId, notification);
return true;
} catch (error) {
console.error(`Notification attempt ${attempt} failed:`, error);
if (attempt === maxRetries) {
console.error('Max retries reached, giving up');
return false;
}
// Wait before retry (exponential backoff)
const delay = Math.pow(2, attempt) * 1000;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
return false;
}import { describe, it, expect, beforeEach } from 'vitest';
import { getAsterismsBackendSDK } from '@asterisms/sdk-backend';
import { createNotification } from '@asterisms/sdk-backend/notifications';
describe('Notification Service', () => {
let sdk: AsterismsBackendSDK;
beforeEach(async () => {
sdk = getAsterismsBackendSDK(testProps);
await sdk.boot();
});
it('should send notification to account', async () => {
const notifications = sdk.notifications();
const notificationData = {
sender: 'test-app',
subject: 'Test Notification',
text: 'This is a test message',
type: 'info' as const
};
await expect(notifications.sendToUser('test-user-id', notification)).resolves.not.toThrow();
});
it('should send notification to workspace', async () => {
const notifications = sdk.notifications();
const notification = {
title: 'Workspace Update',
message: 'Test workspace notification',
type: 'announcement' as const
};
await expect(
notifications.sendToWorkspace('test-workspace-id', notification)
).resolves.not.toThrow();
});
});import { test, expect } from '@playwright/test';
test('send notification through UI', async ({ page }) => {
await page.goto('/notifications');
// Fill notification form
await page.fill('[placeholder="Account ID"]', 'test-account');
await page.fill('[placeholder="Notification subject"]', 'Test Subject');
await page.fill('[placeholder="Notification content"]', 'Test message');
// Send notification
await page.click('button:has-text("Send Notification")');
// Verify success
await expect(page.locator('.success-message')).toBeVisible();
});function validateNotification(notificationData: CreateNotificationOptions): boolean {
if (!notificationData.sender || notificationData.sender.trim().length === 0) {
return false;
}
if (!notificationData.subject || notificationData.subject.trim().length === 0) {
return false;
}
if (!notificationData.text || notificationData.text.trim().length === 0) {
return false;
}
if (notificationData.subject.length > 100) {
return false;
}
if (notificationData.text.length > 1000) {
return false;
}
return true;
}// Example rate limiting for notifications
const notificationLimiter = new Map<string, number>();
async function sendNotificationWithRateLimit(
accountId: string,
notificationData: CreateNotificationOptions
) {
const now = Date.now();
const lastSent = notificationLimiter.get(accountId) || 0;
const cooldown = 60000; // 1 minute cooldown
if (now - lastSent < cooldown) {
throw new Error('Rate limit exceeded');
}
const notifications = sdk.notifications();
const notification = createNotification(notificationData);
await notifications.sendToAccount(accountId, notification);
notificationLimiter.set(accountId, now);
}const categories = {
SYSTEM: 'system',
SECURITY: 'security',
SOCIAL: 'social',
MARKETING: 'marketing',
TASK: 'task'
};
await notifications.sendToAccount(
accountId,
createNotification({
sender: 'your-app',
subject: 'Security Alert',
text: 'Unusual login activity detected',
urgency: 'ERROR'
})
);await notifications.sendToAccount(
accountId,
createNotification({
sender: 'your-app',
subject: 'Task Assigned',
text: 'A new task has been assigned to you',
urgency: 'NONE'
// Additional metadata can be passed through the notification system
// Check the SDK documentation for supported metadata fields
})
);