Notification Services

Learn how to send notifications to accounts and workspaces using the Asterisms JS SDK Backend.

Overview

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.

Basic Usage

Getting the Notification Service

const notifications = sdk.notifications();

Send Notification to Account

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);
}

Send Notification to Workspace

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);
}

Notification Types

Notification Structure

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;
}

Notification Examples

// 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);

SvelteKit Integration

Notification API Route

// 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 });
  }
};

Notification Component

<!-- 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>

Usage in Page

<!-- 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>

Advanced Features

Bulk Notifications

// 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);

Notification Templates

// 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'))
);

Scheduled Notifications

// 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'
});

Error Handling

Notification Errors

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);
  }
}

Retry Logic

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;
}

Testing Notifications

Unit Tests

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();
  });
});

Integration Tests

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();
});

Best Practices

1. Validate Input

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;
}

2. Rate Limiting

// 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);
}

3. Categorize Notifications

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'
  })
);

4. Use Metadata

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
  })
);

Next Steps