import { type ServerResponse } from "node:http"; import type { CommsChannel, CommsInlineImage } from "./comms-types.js"; /** One send, normalized across providers. `body` is html-preferred, else text. */ export interface NormalizedSend { from: string; to: string[]; subject?: string; body: string; inlineImages?: CommsInlineImage[]; } /** * A provider wire-shape adapter. Vendor-neutral seam: the server owns the HTTP + routing + bus * delivery; a profile only knows how ONE provider (or a custom app) shapes its send request. */ export interface EmailSendProfile { /** Descriptive name (e.g. "generic", "sendgrid"). Not a dependency — just a label. */ name: string; /** Request paths (method POST) this profile accepts a send on. */ sendPaths: string[]; /** Parse a POST body (already JSON-parsed) into zero+ normalized sends (a batch yields many). */ parse(path: string, body: unknown): NormalizedSend[]; /** Optional provider-faithful success response. Default: 200 `{ id }` (single) / `{ data:[{id}] }`. */ respond?: (res: ServerResponse, ids: string[], batch: boolean) => void; } export interface EmailCatchServer { /** Point the app's email-API base URL here (loopback only). */ readonly url: string; readonly port: number; /** Every normalized send accepted this run (runtime-only; for inspection/tests). */ readonly received: NormalizedSend[]; close(): Promise; } export interface EmailCatchOptions { /** Bind host. Default 127.0.0.1 (loopback ONLY). */ host?: string; /** Port. Default 0 (ephemeral). */ port?: number; /** Wire-shape profiles, tried in order by path. Default [genericEmailProfile, sendgridEmailProfile]. */ profiles?: EmailSendProfile[]; /** Deterministic id for responses (tests). Default a zero-padded counter. */ idFor?: (n: number) => string; } /** The flat-JSON shape: `{ from, to, subject, html, text }`. Matches Resend (POST /emails) AND a * custom app that sends the common shape AND Postmark's TitleCase keys (From/To/HtmlBody/…). */ export declare const genericEmailProfile: EmailSendProfile; /** SendGrid's nested shape: `from.email`, `personalizations[].to[].email`, `content[].value`. Proves * the seam handles a structurally different vendor, not just a field-name rename. */ export declare const sendgridEmailProfile: EmailSendProfile; export declare const DEFAULT_EMAIL_PROFILES: EmailSendProfile[]; /** * Start the vendor-neutral email catch, routing accepted sends into `channel`. Returns the loopback * URL to hand the app as its email-API base URL. `close()` in a finally (mirror by-id teardown). */ export declare function startEmailCatchServer(channel: CommsChannel, options?: EmailCatchOptions): Promise;