/** * Minimal SMTP submission client over an injectable transport socket. * * Scope and honest boundaries * ──────────────────────────── * Supported: * - EHLO negotiation * - AUTH PLAIN and AUTH LOGIN * - MAIL FROM / RCPT TO / DATA with RFC 2821 dot-stuffing * - A generated RFC 5322 `Message-ID` on every send, returned to the caller * along with the moment the server accepted the message * - QUIT * - TLS-direct (port 465) via `createSmtpTlsSocket()` * - STARTTLS upgrade (port 587) via `createSmtpStartTlsSocket()` * * Not supported (document boundaries): * - HTML, MIME multipart, attachments * - Multiple recipients in a single session (call sendMail per recipient) * - DSN / delivery-status-notification extensions * - PIPELINING (all commands are sent sequentially and await a reply) * - Credentials are never logged * * Transport injection * ──────────────────── * The `socket` parameter accepts any net.Socket so that unit tests can supply * a plain in-process fake socket instead of a real TLS connection. Both socket * factories named above live in the sibling `email/node` entry, so importing * this module never opens a connection or pulls `node:tls` in behind it. */ import type { Socket } from 'node:net'; export interface SmtpClientOptions { readonly socket: Socket; readonly hostname: string; readonly username: string; readonly password: string; readonly timeoutMs?: number; } export interface SmtpSendOptions { readonly from: string; readonly to: string; readonly subject: string; readonly body: string; } /** What a completed send is afterwards identifiable by. */ export interface SmtpSendResult { /** * The `Message-ID` header this send actually carried, angle brackets * included. It is generated here, written into the message, and handed back *, the same string on the wire and in the return value, because its whole * purpose is to correlate with what left the machine and with the * `In-Reply-To` of whatever comes back. */ readonly messageId: string; /** * ISO-8601 instant the server ACCEPTED the message, read after the final * `250`, not when the attempt started, so it records a send that happened * rather than one that was tried. */ readonly sentAt: string; } /** Per-operation timeout, and the connect timeout used by the node adapter. */ export declare const SMTP_DEFAULT_TIMEOUT_MS = 15000; /** * Validate a single SMTP envelope address (MAIL FROM / RCPT TO value). * Rejects: control characters (\r, \n, any C0 or C1), spaces, angle brackets, * comma-separated lists. Only a single bare address is accepted. * * @throws Error with a plain-language message on invalid input. */ export declare function validateSmtpAddress(address: string, field: string): void; /** * Validate an SMTP message Subject header value. * Rejects control characters (\r, \n, etc.) that could split headers. * * @throws Error with a plain-language message on invalid input. */ export declare function validateSmtpSubject(subject: string): void; export declare class SmtpClient { private readonly options; constructor(options: SmtpClientOptions); /** * Connect, negotiate EHLO, and authenticate, then QUIT without sending any * mail. Used to verify SMTP credentials/host reachability (a connect-wizard * "test connection" step) without the side effect of an actual send. * Throws with a plain-language message on any failure stage. */ verifyAuth(): Promise; /** * Send a plain-text email. * Callers must ensure the caller-side confirms the send before calling this. * * Returns the `Message-ID` the message actually carried and the instant the * server accepted it. Nothing about either value is invented after the fact: * the id is written into the headers that go out, and the timestamp is read * once the final `250` has arrived. */ sendMail(opts: SmtpSendOptions): Promise; /** Read the server greeting and negotiate EHLO. Shared by sendMail and verifyAuth. */ private greetAndEhlo; private authenticate; } //# sourceMappingURL=smtp-client.d.ts.map