Skip to Content
ServersNotificationsNotification Provider Packages

Notification Provider Packages

All concrete provider implementations live under packages/providers/. The apps/servers/notifications server imports from these packages and never calls provider APIs directly.


Package Inventory

packages/providers/ |-- sendgrid/ @leadmetrics/provider-sendgrid Live (unit + integration tests) |-- smtp/ @leadmetrics/provider-smtp Live (unit tests) |-- ses/ @leadmetrics/provider-ses Live (unit + integration tests) |-- whatsapp/ @leadmetrics/provider-whatsapp Live (unit + integration tests) |-- telegram/ @leadmetrics/provider-telegram Live (unit + integration tests) |-- msg91/ @leadmetrics/provider-msg91 Planned +-- twilio/ @leadmetrics/provider-twilio Planned

Shared Interface � Email

All three email provider packages export the same EmailProvider interface. The notifications server handler (email.handler.ts) depends only on this interface.

interface EmailMessage { to: { name: string; email: string }[]; subject: string; html: string; text?: string; replyTo?: { name: string; email: string }; attachments?: { filename: string; content: Buffer; contentType: string }[]; } interface EmailSendResult { messageId: string; provider: string; } interface EmailProvider { readonly name: string; send(message: EmailMessage): Promise<EmailSendResult>; }

@leadmetrics/provider-sendgrid

FieldValue
ClassSendGridProvider
Provider slugsendgrid
External dep@sendgrid/mail
Config typeSendGridConfig { apiKey, fromAddress, fromName? }
Testssrc/__tests__/sendgrid.test.ts (unit, vi.mock)
Integration testssrc/__tests__/sendgrid.integration.test.ts

Usage:

import { SendGridProvider } from "@leadmetrics/provider-sendgrid"; const provider = new SendGridProvider({ apiKey: "SG.xxx", fromAddress: "noreply@acme.com" }); const result = await provider.send({ to: [{ name: "Alice", email: "alice@acme.com" }], subject: "Hi", html: "<p>Hi</p>" });

@leadmetrics/provider-smtp

FieldValue
ClassSmtpProvider
Provider slugsmtp
External depnodemailer
Config typeSmtpConfig { host, port, secure, user, pass, fromAddress, fromName? }
Testssrc/__tests__/smtp.test.ts (unit, vi.mock nodemailer)

Supports any SMTP server � Gmail, Outlook 365, self-hosted Postfix, etc.


@leadmetrics/provider-ses

FieldValue
ClassSesProvider
Provider slugses
External dep@aws-sdk/client-ses + nodemailer
Config typeSesConfig { accessKeyId, secretAccessKey, region, fromAddress, fromName? }
Testssrc/__tests__/ses.test.ts (unit) + integration (skipped without AWS creds)

Uses nodemailer’s SES transport so multipart MIME (text + HTML parts) works correctly.


@leadmetrics/provider-whatsapp

FieldValue
ClassWhatsAppBusinessProvider
Provider slugwhatsapp_business_api
External depaxios
Config typeWhatsAppBusinessConfig { apiKey, phoneNumberId, baseUrl? }
TestsUnit + integration (skipped without Meta credentials)

Calls Meta Graph API POST /<phoneNumberId>/messages.
Only pre-approved template messages are supported � free-form text is not allowed by Meta.

interface WhatsAppProvider { readonly name: string; sendTemplate(message: { to: string; // E.164 template: { name: string; languageCode: string }; variables: Record<string, string>; }): Promise<WhatsAppSendResult>; }

@leadmetrics/provider-telegram

FieldValue
ClassTelegramBotProvider
Provider slugtelegram_bot
External depaxios
Config typeTelegramBotConfig { botToken, defaultChatId?, baseUrl? }
TestsUnit + integration (skipped without bot token)

Calls Telegram Bot API POST /sendMessage.

interface TelegramProvider { readonly name: string; send(message: { chatId: string | number; text: string; parseMode?: "HTML" | "Markdown" | "MarkdownV2"; disableWebPagePreview?: boolean; }): Promise<TelegramSendResult>; }

Running Tests

# All provider unit tests pnpm --filter "@leadmetrics/provider-*" test # Single package pnpm --filter @leadmetrics/provider-sendgrid test # Integration tests (requires .env.test.local with real credentials) pnpm --filter @leadmetrics/provider-sendgrid test:integration pnpm --filter @leadmetrics/provider-telegram test:integration

Integration tests are skipped (.skip) when the required credentials are absent.


Adding a New Provider Package

Use an existing package as a template (e.g. packages/providers/sendgrid/).

  1. Create packages/providers/<name>/ with package.json, tsconfig.json, vitest.config.ts, src/types.ts, src/index.ts, src/<name>.ts
  2. Implement the appropriate provider interface (EmailProvider, WhatsAppProvider, etc.)
  3. Export the class and config type from src/index.ts
  4. Add unit tests using vi.hoisted() for mock initialisation before imports
  5. Add case "<slug>": to resolver.ts in apps/servers/notifications
  6. Update .env.example with any new config fields

© 2026 Leadmetrics — Internal use only