Email SDK
Adapters

SMTP

Configure generic SMTP for PurelyMail or a custom server.

The SMTP adapter is built in. It uses Node/Bun TCP and TLS sockets directly, so it does not need Nodemailer.

SMTP
SMTP
@opencoredev/email-sdk/smtp
OfficialNo provider APIBuilt-in transport
Open website

When auth is configured with secure: false, the adapter sends STARTTLS before authenticating. Set allowInsecureAuth: true only for a trusted local server that does not support TLS.

Before live sends

Confirm the SMTP host, port, TLS mode, username, and password with your provider. SMTP is a good fallback for simple transactional messages, but it does not support provider-only fields such as tags, metadata, or attachments.

Configure

import { createEmailClient } from "@opencoredev/email-sdk";
import { smtp } from "@opencoredev/email-sdk/smtp";

const email = createEmailClient({
  adapters: [
    smtp({
      host: "smtp.purelymail.com",
      port: 587,
      secure: false,
      auth: {
        user: process.env.SMTP_USER!,
        pass: process.env.SMTP_PASS!,
      },
    }),
  ],
});

Send

const result = await email.send({
  from: "Acme <hello@example.com>",
  to: "user@example.com",
  cc: "billing@example.com",
  replyTo: "support@example.com",
  subject: "SMTP delivery test",
  text: "It works",
  headers: {
    "X-Trace-ID": "trace_123",
  },
});

console.log(result.provider, result.accepted);

SMTP supports address fields and headers. It rejects attachments, tags, and metadata because those provider-specific fields cannot be represented in this built-in transport.

Options

OptionTypeRequiredNotes
hoststringYesSMTP host.
portnumberNoCommon values are 587, 465, and 25.
securebooleanNoUse TLS from the start of the connection.
auth{ user: string; pass: string }NoSMTP credentials.
defaults{ replyTo?: string }NoDefault reply-to header.
tlsRecord<string, unknown>NoTLS options.
requireTLSbooleanNoRequire STARTTLS.
allowInsecureAuthbooleanNoOpt in to SMTP AUTH without TLS.
namestringNoDefaults to smtp.
heloNamestringNoEHLO name sent to the server.
timeoutMsnumberNoSocket timeout.

Multiple SMTP routes

Rename adapters when you have multiple SMTP routes.

const email = createEmailClient({
  adapters: [
    smtp({
      name: "purelymail",
      host: "smtp.purelymail.com",
      auth: {
        user: process.env.PURELYMAIL_USER!,
        pass: process.env.PURELYMAIL_PASS!,
      },
    }),
    smtp({
      name: "backup-smtp",
      host: "smtp.backup.example",
      auth: {
        user: process.env.BACKUP_SMTP_USER!,
        pass: process.env.BACKUP_SMTP_PASS!,
      },
    }),
  ],
  defaultAdapter: "purelymail",
  fallback: ["backup-smtp"],
});

Response

The adapter returns the SMTP server message ID when it can parse one. It also returns accepted recipients and an empty rejected array because failed recipients throw during the SMTP conversation.

CLI smoke test

npx email-sdk send \
  --adapter smtp \
  --host smtp.purelymail.com \
  --user hello@example.com \
  --pass "$SMTP_PASS" \
  --from "Acme <hello@example.com>" \
  --to user@example.com \
  --subject "SMTP test" \
  --text "It works"

On this page