Notifications

Notification Service

An Effect notification dispatcher that sends dynamic channel payloads through the installed notification channels matching each top-level message key.

Install
Add this registry item to your app with shadcn.
bunx --bun shadcn@latest add https://krakstack.net/r/service-notification.json

Overview

service-notification provides the core Effect notification dispatcher. Messages are dynamic objects where each top-level key is a channel capability, such as email, discord, sns, or push. Installed channels declare the key they handle, contribute their payload type through NotificationChannels, and validate their own payloads.

The core package intentionally does not include provider-specific dependencies. Install separate channel registry entries, such as notification-channel-email-ses, to add transports.

Included Files

  • services/notification/index.ts - Main NotificationService interface and layer helpers
  • services/notification/channels/index.ts - Channel contract and channel registry layer
  • services/notification/schema.ts - Dynamic message envelope and shared send error

Usage

import { Effect, Layer } from "effect";
import { NotificationService } from "@/services/notification";
import { NotificationChannelRegistry } from "@/services/notification/channels";
import { SesNotificationChannel } from "@/services/notification/channels/ses";

const notificationChannelsLayer = Layer.effect(
  NotificationChannelRegistry,
  Effect.gen(function* () {
    const email = yield* SesNotificationChannel;
    return NotificationChannelRegistry.make(email);
  }),
).pipe(Layer.provide(SesNotificationChannel.layer));

const notificationServiceLayer = NotificationService.layer.pipe(
  Layer.provide(notificationChannelsLayer),
);

const program = Effect.gen(function* () {
  const notifications = yield* NotificationService;

  yield* notifications.send({
    email: {
      to: "[email protected]",
      subject: "Deploy succeeded",
      html: "<h1>main -> production</h1><p>All checks passed.</p>",
    },
  });
});

await Effect.runPromise(
  program.pipe(Effect.provide(notificationServiceLayer)),
);

Typed Messages

NotificationService.send is typed from the installed channel declarations. Channel packages extend the core NotificationChannels interface with their key and payload type. For example, the SES channel contributes email: SesEmailNotification, so send({ email: ... }) is checked against the SES payload schema at compile time.

Unknown keys are rejected by TypeScript once at least one channel augments NotificationChannels; missing runtime wiring still fails with NotificationSendError.

Channel Behavior

For each key in the message, the dispatcher sends the payload to every installed channel with the same key. This allows multiple providers to share a capability, such as primary and audit email transports. If a message includes a key with no installed channel, the send fails with NotificationSendError so notification wiring problems are not silently ignored.

Install additional channel entries to enable more keys without changing the core service.

Merging Channels

Use NotificationChannelRegistry.make(...) when you need one NotificationService layer with several channel implementations. Each channel package should expose its channel service layer, then the app-level registry can collect the installed channel instances.

import { Effect, Layer } from "effect";
import { NotificationChannelRegistry } from "@/services/notification/channels";
import { NotificationService } from "@/services/notification";
import { SesNotificationChannel } from "@/services/notification/channels/ses";
import { DiscordNotificationChannel } from "@/services/notification/channels/discord";

const notificationChannelsLayer = Layer.effect(
  NotificationChannelRegistry,
  Effect.gen(function* () {
    const email = yield* SesNotificationChannel;
    const discord = yield* DiscordNotificationChannel;

    return NotificationChannelRegistry.make(email, discord);
  }),
).pipe(
  Layer.provideMerge(SesNotificationChannel.layer),
  Layer.provideMerge(DiscordNotificationChannel.layer),
);

export const notificationServiceLayer = NotificationService.layer.pipe(
  Layer.provide(notificationChannelsLayer),
);

A message can now include every merged capability in one send.

yield* notifications.send({
  email: {
    to: "[email protected]",
    subject: "Deploy succeeded",
    text: "All checks passed.",
  },
  discord: {
    content: "Deploy succeeded.",
  },
});
Dependencies
Packages and shadcn components required by this registry item.

Dependencies