Documentation for agents: an index at /llms.txt, every docs page in one file at /llms-full.txt, and Markdown for any docs page by adding .md to its URL.

Effect KitEmails

SMTP (Nodemailer)

Send email through Nodemailer with the @effect-kit/emails/nodemailer Layer.

The @effect-kit/emails/nodemailer subpath sends messages over SMTP with Nodemailer and provides the Email service.

Installation

npm install @effect-kit/emails effect nodemailer@10.2.1

The SDK is a pinned optional peer. It is needed only when you import this subpath.

Setup

Nodemailer.layerConfig() reads these variables:

VariableRequiredValue
SMTP_HOSTyesSMTP server host
SMTP_PORTnoPort. Defaults to 587
SMTP_SECUREnoUse TLS from the start. Defaults to false
SMTP_USERyesUsername
SMTP_PASSWORDyesPassword

The default Config requires SMTP_USER and SMTP_PASSWORD. For a server without authentication, pass your own Config or use layer with a transport that has no auth.

Example (Sending a Message With Nodemailer)

import { , ,  } from "effect";
import {  } from "@effect-kit/emails";
import * as  from "@effect-kit/emails/nodemailer";

const  = .().(
  .(.(.())),
);

const  = .(function* () {
  const  = yield* ;
  return yield* .({
    : "hello@example.com",
    : "ada@example.com",
    : "Welcome",
    : "Your account is ready.",
  });
});

.(.(.()));

Use a from address your Nodemailer account is allowed to send from.

Options

Nodemailer.layer takes these options. layerConfig builds the same object from a Config.

Prop

Type

Capabilities

Nodemailer reports this in email.capabilities:

TextHTMLBothSender nameCcBccReply-toHeadersAttachmentsInline imagesIdempotency
yesyesyesyesyesyesmultipleyesyesyesno

These fail with UnsupportedFeatureError and acceptance: "notSubmitted" before any request:

  • An idempotencyKey (feature idempotency).

Idempotency

Not supported. A key fails with UnsupportedFeatureError before sending.

Provider notes

  • The owned transport disables file and URL access, turns Nodemailer logging off and sets maxRequeues: 0, so Nodemailer never requeues a message. Closing the Layer's scope closes the transport and waits for in-flight sends.
  • SMTP accepts or rejects each recipient. When some recipients are rejected, the receipt has status: "partiallyAccepted" with one outcome per recipient, and rejected ones carry rejectionCode SMTP_<code>. When every recipient is rejected, the send fails with RecipientRejectedError.
  • The receipt's reference has kind smtpMessageId, taken from the server's message id.

Using your own client

layerClient takes any object with sendMail(message), which a Nodemailer transporter has. options is optional. If the transporter exposes options and pool is true, maxRequeues must be 0, otherwise Layer construction fails with field: "retries".

Example (Borrowing a Nodemailer Client)

import { createTransport } from "nodemailer";
import * as Nodemailer from "@effect-kit/emails/nodemailer";

const transporter = createTransport({
  host: "smtp.example.com",
  port: 587,
  maxRequeues: 0,
});

const EmailLive = Nodemailer.layerClient(transporter);

You own a borrowed client. The Layer never closes it, and the client keeps its own retry, timeout and logging policy.

Errors

Layer construction fails with NodemailerConfigurationError, whose field names the bad option. layerClient fails with it for the client options.

fieldRaised when
transportThe transport options are invalid, for example a bad host, port, a non-positive pool size or duration, an empty auth value, or an unknown property.
timeoutThe timeout is not finite and positive.
retriesA borrowed pooled transporter has maxRequeues other than 0, or its options are not readable.
Nodemailer failureReason
SMTP response code below 500ProviderUnavailableError
EAUTH, ENOAUTH, EOAUTH2, or an AUTH command failureAuthenticationError
RCPT TO failure with an SMTP responseRecipientRejectedError listing the rejected recipients
Any other SMTP 5xx responseMessageRejectedError
ETIMEDOUTTimeoutError
ESOCKET, ECONNECTION, EDNS, ETLS, EPROTOCOLTransportError
Any other known codeProviderError

The Nodemailer error code is kept as providerCode and the SMTP response code as statusCode. Failures before submission (connection, DNS, TLS, greeting, authentication) are acceptance: "notSubmitted". An SMTP response code makes it rejected. Anything else is unknown.