Files
skills/docs/communication/emails/emailer/README.md
2026-05-29 08:20:56 +01:00

6.8 KiB

@suaveplan/emailer - Universal email service with plugin transports (Dummy / SMTP via injectable SmtpClientLike / Gmail REST via FetchLike), nine lifecycle hooks, retry, queue dispatch, Zod-validated boundaries, OpenTelemetry-instrumented hot paths, injectable Clock/Rng/Logger/FetchLike/TimersLike, stable defineCodes('EMAILER', [...]) error registry.

Universal email service with pluggable transports. Ships EmailService, three transports (Dummy / SMTP via injectable SmtpClientLike / Gmail REST via injectable FetchLike), nine lifecycle hooks, deterministic retry, structural queue dispatch, Zod-validated option boundaries, OpenTelemetry-instrumented hot paths, and a stable defineCodes("EMAILER", [...]) error registry.

Documentation tree

  • email-service — EmailService<TTransport> is the public entry point for @suaveplan/emailer. It orchestrates message validation, life…
    • base-email-transport — BaseEmailTransport is the abstract helper that every concrete transport (DummyTransport, SmtpTransport, `GmailRes…
    • email-service — EmailService<TTransport> is the public entry point for @suaveplan/emailer. It orchestrates message validation, life…
  • retry — Deterministic retry helper used by EmailService.send. The helper takes a RetryPolicy, an injectable TimersLike, a…
    • retry — Deterministic retry helper used by EmailService.send. The helper takes a RetryPolicy, an injectable TimersLike, a…
  • validate — Shared address-formatting and message-validation helpers used by every concrete transport and by EmailService itself.…
    • validate — Shared address-formatting and message-validation helpers used by every concrete transport and by EmailService itself.…
  • error-codes — @suaveplan/emailer ships a single stable error-code registry that every failure path in the package routes through. T…
    • error-codes — @suaveplan/emailer ships a single stable error-code registry that every failure path in the package routes through. T…
  • fake-fetch — Deterministic test double for FetchLike, used by GmailRestTransport tests. Queues scripted responses and captures e…
  • fake-smtp-client — Deterministic test double for SmtpClientLike. Tests queue scripted responses, invoke the wrapped SmtpTransport, and…
  • otel — Single source of truth for every OpenTelemetry instrument emitted by @suaveplan/emailer. Every production path that t…
    • metrics — Single source of truth for every OpenTelemetry instrument emitted by @suaveplan/emailer. Every production path that t…
  • schemas — Centralised Zod schemas for every public option bundle and wire payload in @suaveplan/emailer. The schemas exist to e…
    • schemas — Centralised Zod schemas for every public option bundle and wire payload in @suaveplan/emailer. The schemas exist to e…
  • dummy — DummyTransport is a deterministic in-memory transport designed for tests, fixtures, and local development. It records…
    • dummy-transport — DummyTransport is a deterministic in-memory transport designed for tests, fixtures, and local development. It records…
  • gmail-rest — GmailRestTransport sends mail through Gmail's REST API directly, over an injectable FetchLike. The transport builds…
    • gmail-rest-transport — GmailRestTransport sends mail through Gmail's REST API directly, over an injectable FetchLike. The transport builds…
  • smtp — SmtpTransport is a structural SMTP transport: it accepts a caller-supplied SmtpClientLike adapter (the contract is…
    • smtp-transport — SmtpTransport is a structural SMTP transport: it accepts a caller-supplied SmtpClientLike adapter (the contract is…

Why universal

The upstream @suaveplan/emailer was server-only because it imported nodemailer and googleapis directly. The rewrite drops both hard dependencies in favour of injectable contracts:

  • SMTP: callers supply a thin SmtpClientLike adapter so nodemailer (or any other library) lives in the consumer's app shell, not in this package.
  • Gmail: send is implemented directly against the Gmail REST API via FetchLike, so token acquisition is the caller's concern and the transport runs unchanged in Bun, Node, and the browser.

Modules

Quick start

import { EmailService } from "@suaveplan/emailer";
import { createDummyTransport } from "@suaveplan/emailer/transports/dummy";

const transport = createDummyTransport();
const service = new EmailService({
    transport,
    transportOptions: {},
    defaultFrom: "no-reply@example.com",
});
await service.initialize();
const result = await service.send({
    to: "user@example.com",
    subject: "hello",
    html: "<p>hi</p>",
});

License

MIT