import type { SendEmailOptions, SendPushOptions, SendWebhookOptions, ScheduleOptions, NotificationResponse, BatchNotificationResponse, Template, RenderedTemplate, } from './types'; export interface NotifyClientOptions { serviceUrl: string; serviceKey: string; appId: string; timeout?: number; } export class NotifyClient { private readonly serviceUrl: string; private readonly serviceKey: string; private readonly appId: string; private readonly timeout: number; constructor(options: NotifyClientOptions) { this.serviceUrl = options.serviceUrl.replace(/\/$/, ''); this.serviceKey = options.serviceKey; this.appId = options.appId; this.timeout = options.timeout || 30000; } // ==================== Notifications ==================== /** * Send an email notification */ async sendEmail(options: SendEmailOptions): Promise { const payload: Record = { channel: 'email', appId: this.appId, recipient: options.to, template: options.template, subject: options.subject, body: options.body, data: options.data, priority: options.priority, externalId: options.externalId, }; // Only include emailOptions if from or replyTo is provided if (options.from || options.replyTo) { payload.emailOptions = { ...(options.from && { from: options.from }), ...(options.replyTo && { replyTo: options.replyTo }), }; } return this.send(payload); } /** * Send a push notification */ async sendPush(options: SendPushOptions): Promise { return this.send({ channel: 'push', appId: this.appId, userId: options.userId, recipient: options.token, recipients: options.tokens, subject: options.title, body: options.body, data: options.data, pushOptions: { sound: options.sound, badge: options.badge, channelId: options.channelId, }, priority: options.priority, externalId: options.externalId, }); } /** * Send a webhook notification */ async sendWebhook(options: SendWebhookOptions): Promise { return this.send({ channel: 'webhook', appId: this.appId, recipient: options.url, data: options.body, webhookOptions: { method: options.method, headers: options.headers, timeout: options.timeout, }, priority: options.priority, externalId: options.externalId, }); } /** * Schedule an email notification */ async scheduleEmail(options: SendEmailOptions & ScheduleOptions): Promise { const payload: Record = { channel: 'email', appId: this.appId, recipient: options.to, template: options.template, subject: options.subject, body: options.body, data: options.data, priority: options.priority, externalId: options.externalId, scheduledFor: options.scheduledFor instanceof Date ? options.scheduledFor.toISOString() : options.scheduledFor, }; // Only include emailOptions if from or replyTo is provided if (options.from || options.replyTo) { payload.emailOptions = { ...(options.from && { from: options.from }), ...(options.replyTo && { replyTo: options.replyTo }), }; } return this.schedule(payload); } /** * Schedule a push notification */ async schedulePush(options: SendPushOptions & ScheduleOptions): Promise { return this.schedule({ channel: 'push', appId: this.appId, userId: options.userId, recipient: options.token, recipients: options.tokens, subject: options.title, body: options.body, data: options.data, pushOptions: { sound: options.sound, badge: options.badge, channelId: options.channelId, }, priority: options.priority, externalId: options.externalId, scheduledFor: options.scheduledFor instanceof Date ? options.scheduledFor.toISOString() : options.scheduledFor, }); } /** * Send multiple notifications in batch */ async sendBatch( notifications: Array< | ({ type: 'email' } & SendEmailOptions) | ({ type: 'push' } & SendPushOptions) | ({ type: 'webhook' } & SendWebhookOptions) > ): Promise { const mapped = notifications.map((n) => { if (n.type === 'email') { return { channel: 'email' as const, appId: this.appId, recipient: n.to, template: n.template, subject: n.subject, body: n.body, data: n.data, priority: n.priority, externalId: n.externalId, }; } else if (n.type === 'push') { return { channel: 'push' as const, appId: this.appId, userId: n.userId, recipient: n.token, recipients: n.tokens, subject: n.title, body: n.body, data: n.data, priority: n.priority, externalId: n.externalId, }; } else { return { channel: 'webhook' as const, appId: this.appId, recipient: n.url, data: n.body, priority: n.priority, externalId: n.externalId, }; } }); const response = await this.request('/notifications/batch', { method: 'POST', body: JSON.stringify({ notifications: mapped }), }); return response; } /** * Get notification status */ async getNotification(id: string): Promise { const response = await this.request<{ notification: NotificationResponse | null }>( `/notifications/${id}` ); return response.notification; } /** * Cancel a pending notification */ async cancelNotification(id: string): Promise { const response = await this.request<{ notification: NotificationResponse }>( `/notifications/${id}`, { method: 'DELETE' } ); return response.notification; } // ==================== Templates ==================== /** * List all templates */ async listTemplates(appId?: string): Promise { const url = appId ? `/templates?appId=${encodeURIComponent(appId)}` : '/templates'; const response = await this.request<{ templates: Template[] }>(url); return response.templates; } /** * Get a template by slug */ async getTemplate(slug: string, locale = 'de-DE'): Promise