@portable/smtp
Functions
Smtp.connect
Opens an SMTP session: connects, reads the greeting, sends EHLO (or HELO), starts TLS and logs in.
async function connect(options: connect.Options): Promise<Client>;| Option | Type | Default | Description |
|---|---|---|---|
connect | Connect | Opens the TCP connection, such as connect from cloudflare:sockets. | |
host | string | ||
port | number | 465 when secure is 'on', otherwise 587 | |
secure | "on" | "starttls" | "off" | 'on' for port 465, otherwise 'starttls' | 'on' starts TLS right away, 'starttls' requires the STARTTLS command, and 'off' never encrypts. |
auth | Auth | ||
allowInsecureAuth | boolean | false | Send credentials over an unencrypted connection. Without it, AUTH requires TLS. |
name | string | '[127.0.0.1]' | The client's host name for EHLO. |
lmtp | boolean | false | Speak LMTP (RFC 2033): LHLO, and one reply per recipient after the message. |
signal | AbortSignal | Limits connecting, the TLS handshake and authentication. Aborting rejects with signal.reason. |
import * as Smtp from '@portable/smtp';
import { connect } from 'cloudflare:sockets';
await using client = await Smtp.connect({
connect,
host: 'smtp.example.com',
auth: { user: 'me@example.com', pass: env.SMTP_PASSWORD },
signal: AbortSignal.timeout(10_000),
});
const result = await client.send({
from: 'me@example.com',
to: ['you@example.com'],
message,
});Smtp.send
Connects, sends one message and closes the connection.
async function send(mail: Mail, options: send.Options): Promise<Result>;| Option | Type | Default | Description |
|---|---|---|---|
connect | Connect | Opens the TCP connection, such as connect from cloudflare:sockets. | |
host | string | ||
port | number | 465 when secure is 'on', otherwise 587 | |
secure | "on" | "starttls" | "off" | 'on' for port 465, otherwise 'starttls' | 'on' starts TLS right away, 'starttls' requires the STARTTLS command, and 'off' never encrypts. |
auth | Auth | ||
allowInsecureAuth | boolean | false | Send credentials over an unencrypted connection. Without it, AUTH requires TLS. |
name | string | '[127.0.0.1]' | The client's host name for EHLO. |
lmtp | boolean | false | Speak LMTP (RFC 2033): LHLO, and one reply per recipient after the message. |
signal | AbortSignal | Limits connecting, the TLS handshake and authentication. Aborting rejects with signal.reason. |
import * as Smtp from '@portable/smtp';
import { connect } from 'cloudflare:sockets';
const result = await Smtp.send(
{
from: 'me@example.com',
to: 'you@example.com',
message: 'Subject: Hi\r\n\r\nHello',
},
{
connect,
host: 'smtp.example.com',
auth: { user: 'me@example.com', pass: env.SMTP_PASSWORD },
},
);Errors
Smtp.CommandError
Thrown when connecting, a command, or a message transfer fails.
class CommandError extends Errors.BaseError {
constructor(options: CommandError.Options);
readonly kind: string;
readonly code: CommandError.Code;
readonly command: string | undefined;
readonly responseCode: number | undefined;
readonly enhancedCode: string | undefined;
readonly response: string | undefined;
readonly rejected: readonly Rejection[] | undefined;
}| Option | Type | Default | Description |
|---|---|---|---|
code | Code | ||
message | string | What failed. The server's reply, if there was one, is appended. | |
command | string | ||
reply | Reply | ||
rejected | readonly Rejection[] | ||
cause | unknown |
import * as Smtp from '@portable/smtp';
try {
await client.send({ from, to, message });
} catch (error) {
if (
error instanceof Smtp.CommandError &&
error.responseCode === 452
) {
retryLater();
} else {
throw error;
}
}Types
Smtp.Rejection
A recipient the server refused, with its reply.
type Rejection = {
recipient: string;
responseCode: number;
enhancedCode: string | undefined;
response: string;
};Smtp.Auth
Credentials for AUTH PLAIN or LOGIN, or an OAuth 2.0 access token for XOAUTH2.
type Auth = {
user: string;
pass: string;
method?: | | undefined;
} | {
user: string;
accessToken: string;
};Smtp.Client
An open SMTP session, from Smtp.connect. Calls run one at a time, in the order they are made.
Where the runtime has Symbol.asyncDispose, await using calls close() at the end of the
block.
type Client = AsyncDisposeMethod & {
readonly secure: boolean;
readonly extensions: readonly string[];
send(mail: Mail, options?: Client.SendOptions): Promise<Result>;
close(options?: Client.CloseOptions): Promise<void>;
};Smtp.Dsn
RFC 3461 delivery status notification parameters. They are sent only when the server offers DSN.
type Dsn = {
ret?: | | undefined;
envid?: string | undefined;
notify?: | readonly ( | | )[] | undefined;
};Smtp.Mail
A message and its envelope.
type Mail = {
from: string;
to: string | readonly string[];
message: ReadableStream<Uint8Array> | Uint8Array | string;
size?: number | undefined;
dsn?: Dsn | undefined;
requireTls?: boolean | undefined;
};Smtp.Result
The outcome of a send that reached at least one recipient.
type Result = {
accepted: readonly string[];
rejected: readonly Rejection[];
response: string;
};Smtp.Connect
Opens a TCP connection. Pass connect from cloudflare:sockets, or an adapter for Deno or Node
(see the README).
type Connect = (
address: {
hostname: string;
port: number;
},
options: SocketOptions,
) => Socket;Smtp.Socket
A TCP connection with the shape of the WinterTC and Cloudflare Workers Sockets API. The socket that
connect from cloudflare:sockets returns fits it as is.
type Socket = {
readonly readable: ReadableStream<Uint8Array>;
readonly writable: WritableStream<Uint8Array>;
readonly opened: Promise<unknown>;
readonly closed: Promise<unknown>;
close(): Promise<unknown> | void;
startTls(): Socket;
};Smtp.SocketOptions
The options Connect receives. cloudflare:sockets reads them as they are.
type SocketOptions = {
secureTransport: | | ;
allowHalfOpen: boolean;
};