import { M as Middleware } from "../packem_shared/types.d-Chhzb-48.js"; export { type S as SendFunction, c as composeMiddleware } from "../packem_shared/types.d-Chhzb-48.js"; import { E as EmailOptions, M as MaybePromise, a as EmailResult, R as Result, c as EmailAddress } from "../packem_shared/types.d-CyiC9uRC.js"; import { d as EventBus } from "../packem_shared/event-bus.d-BNQA-FZE.js"; import { b as SuppressionStore } from "../packem_shared/types.d-BzoHR9nB.js"; import { T as Tracer } from "../packem_shared/tracer.d-sb5yrt_n.js"; import 'node:buffer'; /** * The state of a {@link circuitBreakerMiddleware}. * * `closed` = healthy; `open` = failing fast; `half-open` = trialing one request to test recovery. */ type CircuitState = "closed" | "half-open" | "open"; /** * Options for the {@link circuitBreakerMiddleware}. */ interface CircuitBreakerMiddlewareOptions { /** * Consecutive failures before the circuit opens. * @default 5 */ failureThreshold?: number; /** * Time source in milliseconds — injectable for tests. Defaults to `Date.now`. */ now?: () => number; /** * Notified whenever the circuit changes state. * @param state The new state. */ onStateChange?: (state: CircuitState) => void; /** * How long, in milliseconds, the circuit stays open before allowing a half-open trial. * @default 30000 */ resetTimeout?: number; } /** * Trips a circuit after repeated provider failures, failing fast while open and trialing recovery via * a half-open state — protecting a struggling provider from being hammered. * @param options Breaker configuration. See {@link CircuitBreakerMiddlewareOptions}. * @returns A middleware that fails fast while the provider is unhealthy. */ declare const circuitBreakerMiddleware: (options?: CircuitBreakerMiddlewareOptions) => Middleware; /** * Options for the {@link dedupeMiddleware}. */ interface DedupeMiddlewareOptions { /** * Derives the dedupe key for a message. Defaults to a content hash of recipients + subject + body. * @param options The message being sent. */ keyFn?: (options: EmailOptions) => string; /** * Time source in milliseconds — injectable for tests. Defaults to `Date.now`. */ now?: () => number; /** * How long, in milliseconds, a key is remembered. * @default 60000 */ ttl?: number; } /** * Suppresses duplicate sends of the same message within a TTL window, returning the original result * for repeats instead of re-sending. * * Useful behind at-least-once delivery sources (queues, webhooks) that may replay a message. * @param options Dedupe configuration. See {@link DedupeMiddlewareOptions}. * @returns A middleware that drops duplicate sends within the TTL. */ declare const dedupeMiddleware: (options?: DedupeMiddlewareOptions) => Middleware; /** * Options for {@link withEvents}. */ interface EventsMiddlewareOptions { /** * Generates event ids. Defaults to `crypto.randomUUID`. */ idFactory?: () => string; /** * Time source in milliseconds — injectable for tests. Defaults to `Date.now`. */ now?: () => number; } /** * Emits unified `EmailEvent`s onto an {@link EventBus} around each send: `queued` before, then * `sent` or `failed` after. Combine with webhook-derived events for a single timeline per message. * @param bus The event bus to publish to. * @param options Middleware options. See {@link EventsMiddlewareOptions}. * @returns A middleware that publishes send-lifecycle events. */ declare const withEvents: (bus: EventBus, options?: EventsMiddlewareOptions) => Middleware; /** * Options for the {@link loggingMiddleware}. */ interface LoggingMiddlewareOptions { /** * The logger to write to. * @default console */ logger?: Pick; /** * Redact recipient addresses (e.g. `j••@example.com`) to keep PII out of logs. * @default true */ redact?: boolean; } /** * Logs each send attempt and its outcome, redacting recipient PII by default. * @param options Logging configuration. See {@link LoggingMiddlewareOptions}. * @returns A middleware that logs sends and their results. */ declare const loggingMiddleware: (options?: LoggingMiddlewareOptions) => Middleware; /** * A minimal metrics sink — implement it with prom-client, OpenTelemetry metrics, StatsD, etc. */ interface MetricsRecorder { /** * Increments a counter. * @param metric The metric name. * @param labels Optional labels/dimensions. */ increment: (metric: string, labels?: Record) => void; /** * Records an observation (e.g. a duration) on a histogram/summary. * @param metric The metric name. * @param value The observed value. * @param labels Optional labels/dimensions. */ observe: (metric: string, value: number, labels?: Record) => void; } /** * Options for {@link withMetrics}. */ interface MetricsMiddlewareOptions { /** * Prefix for emitted metric names. * @default "email_" */ prefix?: string; } /** * Records per-send metrics (a `*_sent_total` counter and a `*_duration_ms` observation, labelled by * provider and status) into an injected {@link MetricsRecorder}. Dependency-free — wire it to * prom-client or any metrics backend. * @param recorder The metrics sink. * @param options Middleware options. See {@link MetricsMiddlewareOptions}. * @returns A middleware that records send metrics. */ declare const withMetrics: (recorder: MetricsRecorder, options?: MetricsMiddlewareOptions) => Middleware; /** * A token that may carry an absolute expiry. */ interface CacheableToken { /** * The bearer access token. */ accessToken: string; /** * Absolute expiry time in milliseconds since the epoch. Omit for non-expiring tokens. */ expiresAt?: number; } /** * An OAuth2 access token plus its expiry. */ type OAuth2Token = CacheableToken; /** * Options for the {@link oauth2Middleware}. */ interface OAuth2MiddlewareOptions { /** * Fetches (or refreshes) the access token. Called only when the cached token is missing or within * {@link OAuth2MiddlewareOptions.skewMs} of expiry. Wire this to your Gmail / Microsoft 365 refresh * flow. */ fetchToken: () => Promise | OAuth2Token; /** * Inject the credential into this **message** header. * * `EmailOptions.headers` are MIME headers written onto the outgoing message (see * `buildMimeMessage`), so every recipient — and every hop — sees this value. Providers * authenticate over their own transport (`apiKey` in the HTTP request, SMTP XOAUTH2), never * from here, so there is no legitimate reason to set this to `Authorization`. * * Leave it unset unless a provider genuinely requires a custom credential header on the * message itself. Use {@link OAuth2MiddlewareOptions.onToken} instead. * @default undefined — no header is injected */ headerName?: string; /** * Time source in milliseconds — injectable for tests. Defaults to `Date.now`. */ now?: () => number; /** * Receives every freshly-acquired token — use it to feed SMTP XOAUTH2 or a provider client that * authenticates outside the message headers. * @param token The new token. */ onToken?: (token: OAuth2Token) => void; /** * The auth scheme prefix for the header value. * @default "Bearer" */ scheme?: string; /** * Refresh this many milliseconds before the token actually expires. * @default 60000 */ skewMs?: number; } /** * Acquires an OAuth2 credential for each outgoing message, refreshing it on demand and caching it * until just before expiry. * * The token is handed to the caller through {@link OAuth2MiddlewareOptions.onToken} — the channel * provider clients (Gmail, Microsoft 365, SMTP XOAUTH2) authenticate from. It is **not** written * onto the message unless {@link OAuth2MiddlewareOptions.headerName} is set explicitly, because * `EmailOptions.headers` are MIME headers the recipient can read. * @param options OAuth2 configuration. See {@link OAuth2MiddlewareOptions}. * @returns The middleware. * @throws {TypeError} If neither `onToken` nor `headerName` is supplied — the token would have * nowhere to go and the middleware would silently do nothing but burn token-endpoint calls — or if * `headerName` is empty. */ declare const oauth2Middleware: (options: OAuth2MiddlewareOptions) => Middleware; /** * Options for the {@link rateLimitMiddleware}. */ interface RateLimitMiddlewareOptions { /** * Maximum burst capacity (tokens). Defaults to {@link RateLimitMiddlewareOptions.rate}. */ burst?: number; /** * Time source in milliseconds — injectable for tests. Defaults to `Date.now`. */ now?: () => number; /** * Sustained send rate, in messages per second. */ rate: number; /** * Sleep implementation — injectable for tests. Defaults to `setTimeout`. * @param ms Milliseconds to wait. */ sleep?: (ms: number) => Promise; } /** * Pre-tuned per-second rate presets for common providers' default plans. Override as needed for your * own quota. */ declare const RATE_LIMIT_PRESETS: { readonly "aws-ses": 14; readonly mailgun: 100; readonly postmark: 100; readonly resend: 10; readonly sendgrid: 100; }; /** * Throttles sends with a token-bucket limiter, awaiting capacity rather than failing when the rate is * exceeded. * @param options Rate configuration. See {@link RateLimitMiddlewareOptions} and {@link RATE_LIMIT_PRESETS}. * @returns A middleware that paces sends to the configured rate. */ declare const rateLimitMiddleware: (options: RateLimitMiddlewareOptions) => Middleware; /** * Wraps a transform that rewrites the message before it is sent — e.g. running the HTML through the * render helpers (preheader, dark-mode, CID rewrite) and the opt-in CSS inliner. * @param transform Maps the resolved {@link EmailOptions} to its rendered form. * @returns A middleware that applies the transform ahead of the provider call. * @example * ```ts * import { injectPreheader } from "@visulima/email/render/preheader"; * import { addDarkModeSupport } from "@visulima/email/render/dark-mode"; * * mail.use(withRender(async (email) => ({ * ...email, * html: email.html ? addDarkModeSupport(injectPreheader(email.html, "Hi!")) : email.html, * }))); * ``` */ declare const withRender: (transform: (options: EmailOptions) => MaybePromise) => Middleware; /** * Options for the {@link retryMiddleware}. */ interface RetryMiddlewareOptions { /** * The base backoff delay in milliseconds. Actual delays grow exponentially and are jittered. * @default 200 */ baseDelay?: number; /** * Called when all retries are exhausted — a dead-letter hook for durable storage/alerting. * @param options The message that ultimately failed. * @param result The final failed result. */ onDeadLetter?: (options: EmailOptions, result: Result) => Promise | void; /** * Maximum number of attempts (including the first). * @default 3 */ retries?: number; /** * Decides whether a failed result is retryable. Defaults to retrying every failure. * @param result The failed result. */ shouldRetry?: (result: Result) => boolean; /** * Sleep implementation — injectable for tests. Defaults to `setTimeout`. * @param ms Milliseconds to wait. */ sleep?: (ms: number) => Promise; } /** * Retries failed sends with full-jitter exponential backoff, optionally dead-lettering the final * failure. * * Full jitter (`random(0, base * 2^attempt)`) avoids the thundering-herd retries a fixed schedule * causes. * @param options Retry configuration. See {@link RetryMiddlewareOptions}. * @returns A middleware that re-attempts failed sends before giving up. */ declare const retryMiddleware: (options?: RetryMiddlewareOptions) => Middleware; /** * Options for {@link withSuppression}. */ interface SuppressionMiddlewareOptions { /** * Called with the recipients that were removed because they are suppressed. * @param suppressed The dropped recipients. */ onSuppressed?: (suppressed: EmailAddress[]) => void; } /** * Filters suppressed recipients out of the `to` list before sending, and short-circuits with a failed * result when every recipient is suppressed. * @param store The suppression store to check against. * @param options Middleware options. See {@link SuppressionMiddlewareOptions}. * @returns A middleware that enforces the suppression list. */ declare const withSuppression: (store: SuppressionStore, options?: SuppressionMiddlewareOptions) => Middleware; /** * Options for {@link withTelemetry}. */ interface TelemetryMiddlewareOptions { /** * The span name. * @default "email.send" */ spanName?: string; } /** * Wraps each send in an OpenTelemetry span, recording the provider, recipient count and outcome. * * The `@opentelemetry/api` `Tracer` is injected (type-only import here), so this middleware adds no * runtime dependency — pass `trace.getTracer("email")` from your instrumented app. * @param tracer An OpenTelemetry `Tracer`. * @param options Middleware options. See {@link TelemetryMiddlewareOptions}. * @returns A middleware that traces sends. */ declare const withTelemetry: (tracer: Tracer, options?: TelemetryMiddlewareOptions) => Middleware; export { type CircuitBreakerMiddlewareOptions, type CircuitState, type DedupeMiddlewareOptions, type EventsMiddlewareOptions, type LoggingMiddlewareOptions, type MetricsMiddlewareOptions, type MetricsRecorder, type Middleware, type OAuth2MiddlewareOptions, type OAuth2Token, RATE_LIMIT_PRESETS, type RateLimitMiddlewareOptions, type RetryMiddlewareOptions, type SuppressionMiddlewareOptions, type TelemetryMiddlewareOptions, circuitBreakerMiddleware, dedupeMiddleware, loggingMiddleware, oauth2Middleware, rateLimitMiddleware, retryMiddleware, withEvents, withMetrics, withRender, withSuppression, withTelemetry };