# `email-channel` — Email delivery channel ## Purpose `email-channel.ts` ships the `EmailChannel` `DeliveryChannel` implementation. The class is provider-neutral: the integration seam is a caller-supplied `sendFn(message)` callback that performs the actual SMTP / API call. Wiring a real provider (Amazon SES, SendGrid, Postmark, Mailgun, or an internal MTA) is a matter of supplying a function with the documented signature. The channel swallows transport errors and returns them as `DeliveryResult { success: false, error }`, never propagating exceptions to the router. ## Features - Provider-neutral via injectable `sendFn`. - `canSend` accepts `email` channel + `@`-bearing recipient — keeps malformed addresses from reaching upstream providers. - Falls back to `"(No subject)"` when the notification carries no subject. - Propagates `metadata` to the provider message — useful for tracking IDs. - Injectable `Clock` for deterministic `timestamp` fields in tests. ## Usage: Basic ```ts import { EmailChannel } from "@suaveplan/notification-channel"; const channel = new EmailChannel({ from: "noreply@example.com", sendFn: async (msg) => ({ messageId: await sesClient.send(msg) }), }); const result = await channel.send(notification); if (!result.success) reportError(result.error); ``` ## Usage: Advanced ```ts import { EmailChannel } from "@suaveplan/notification-channel"; const channel = new EmailChannel({ from: "alerts@example.com", sendFn: async (msg) => { const messageId = await sendgrid.send({ to: msg.to, from: "alerts@example.com", subject: msg.subject, content: [{ type: "text/plain", value: msg.body }], customArgs: msg.metadata as Record | undefined, }); return { messageId }; }, clock: () => testNow, }); ``` ## API Reference - `EmailChannel(config: EmailChannelConfig)` — constructor. - `EmailChannelConfig` — `{ from, sendFn, clock? }`. - `EmailMessage` — `{ to, subject, body, metadata? }` passed to `sendFn`. - `canSend(n): boolean`, `send(n): Promise` — `DeliveryChannel` impl. ## Implementation Notes The default subject `"(No subject)"` matches the upstream `@suaveplan/notification-channel@1.2.6` behaviour so consumer dashboards keep showing a non-empty subject column. Non-`Error` throws inside `sendFn` are coerced via `String(err)` so the `DeliveryResult.error` is always a string. The injected `clock` only affects the `timestamp` field — the upstream provider's own timestamps remain authoritative for SLA tracking.