Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
# email-channel
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Adapts `@suaveplan/emailer`'s `EmailService` to the
`@suaveplan/notification-channel` `DeliveryChannel` interface so
`NotificationDispatcher` can route email notifications through any of
the emailer's three transports (Dummy / SMTP / Gmail REST). The
channel is registered under the name `email` and canSend-gates on
`notification.channel === "email"`. This module is the single place
where the dispatcher's generic `Notification` shape is translated
into an `EmailMessage` — keeping that translation out of the router
and out of `EmailService`.
## Features
- Channel name `email`; canSend gates on `notification.channel ===
"email"`.
- Maps `Notification.recipient` → `EmailMessage.to` (the emailer's
`AddressBag` accepts a single string), `Notification.subject` →
`EmailMessage.subject` (with `defaultSubject` fallback when the
notification carries no subject), `Notification.body` →
`EmailMessage.text` (or `html` when `metadata.html === true`).
- Folds `EmailService.send`'s `{ success: false, error }` outcomes
into `DeliveryResult.error`. No throws for transport failures.
- Refuses to construct with `emailService: null` — raises
`NotificationsPluginError(EMAIL_SERVICE_UNAVAILABLE)` so the
dispatcher fails fast at boot when an operator forgets to inject
the service.
- Surfaces the emailer's `messageId` when the transport returns one;
falls back to the upstream notification id otherwise.
## Usage: Basic
```ts
import { EmailService, createDummyTransport } from "@suaveplan/emailer";
import { EmailDeliveryChannel } from "@suaveplan/api-plugin-notifications";
const email = new EmailService({
transport: createDummyTransport({ logSends: true }),
});
await email.initialize();
const channel = new EmailDeliveryChannel({ emailService: email });
const result = await channel.send({
id: "notif_1",
channel: "email",
recipient: "user@example.com",
subject: "Welcome",
body: "Hello!",
priority: "normal",
status: "sent",
createdAt: new Date().toISOString(),
});
```
## Usage: Advanced
Render an HTML body, configure a default subject, and wire into the
router:
```ts
import { NotificationRouter } from "@suaveplan/notification-channel";
const channel = new EmailDeliveryChannel({
emailService: email,
defaultSubject: "[YourApp] Notification",
});
const router = new NotificationRouter({
channels: [{ channel: "email", enabled: true }],
defaultChannel: "email",
deliveryChannels: [channel],
});
await router.send({
recipient: "user@example.com",
channel: "email",
body: "<h1>Welcome</h1><p>Glad you're here.</p>",
metadata: { html: true },
});
```
## API Reference
- `class EmailDeliveryChannel implements DeliveryChannel`
- `name: "email"`
- `canSend(n): boolean` — true iff `n.channel === "email"`
- `send(n): Promise<DeliveryResult>`
- `interface EmailDeliveryChannelOptions`
- `emailService: EmailService | null` — null raises
`EMAIL_SERVICE_UNAVAILABLE` at construction
- `defaultSubject?: string` — default `"Notification"`
- `clock?: Clock`
## Implementation Notes
- The emailer's `EmailMessage` requires `subject` to be a non-empty
string; the channel always supplies one (via `defaultSubject` if
the notification omits it).
- `metadata.html === true` flips body delivery from `text` to `html`.
Both formats are mutually exclusive — choose one per notification.
- The channel exposes the emailer's transport-reported `messageId`
on the `DeliveryResult` so callers can correlate delivery-tracking
events with the underlying email message.
- `EmailService.send` never throws (it folds errors into the result),
so the channel doesn't need a try/catch around it.