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

Lettermint

Send email through Lettermint with the @effect-kit/emails/lettermint Layer.

The @effect-kit/emails/lettermint subpath sends messages through Lettermint and provides the Email service.

Installation

npm install @effect-kit/emails effect lettermint@3.2.0

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

Setup

Lettermint.layerConfig() reads these variables:

VariableRequiredValue
LETTERMINT_SENDING_TOKENyesSending token

Example (Sending a Message With Lettermint)

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

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

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

.(.(.()));

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

Options

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

Prop

Type

Capabilities

Lettermint reports this in email.capabilities:

TextHTMLBothSender nameCcBccReply-toHeadersAttachmentsInline imagesIdempotency
yesyesyesyesyesyesmultipleyesyesyesreplay, 24 hours

Every message feature is supported, so no message fails the capability check.

Idempotency

Mode replay, with a retention of 24 hours. The package passes the key to Lettermint as the SDK's idempotencyKey option.

The key must be 1 to 255 printable ASCII characters and not blank, otherwise the send fails with InvalidMessageError (path idempotencyKey) and acceptance: "notSubmitted". A conflict from Lettermint fails with IdempotencyConflictError.

Example (Sending With an Idempotency Key)

import {  } from "effect";
import {  } from "@effect-kit/emails";

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

Provider notes

  • With sandbox: true the provider name becomes lettermint/sandbox, and a response that does not report the sandbox flag fails with UnexpectedResponseError. Without it, a response that reports the flag fails the same way.
  • Inline images need a fully qualified content id. A contentId without @ fails with InvalidMessageError (path attachments) before sending, because Lettermint would otherwise append @lm and change the id you chose.
  • A send is accepted when Lettermint answers with status pending.

Using your own client

layerClient takes any object with emails.send(message, { signal, timeout, idempotencyKey }), which a Lettermint client has. options is optional and accepts timeout and sandbox.

Example (Borrowing a Lettermint Client)

import { Lettermint as LettermintSdk } from "lettermint";
import * as Lettermint from "@effect-kit/emails/lettermint";

const client = new LettermintSdk({ sendingToken: "fake-token" });

const EmailLive = Lettermint.layerClient(client);

You own a borrowed client. The Layer never closes it, and the client keeps its own retry, timeout and logging policy. For a sandbox client, pass { sandbox: true } so the provider name and the response check match.

Errors

ProviderConfigurationError carries provider: "lettermint" and a field naming the bad option. Layer construction fails with it, and layerClient fails with it for the client options.

fieldRaised when
timeoutThe timeout is not finite and positive.
tokenThe sending token is not printable ASCII without spaces.
sendingTokenThe SDK client could not be created from the token.
Lettermint failureReason
SDK conflict errorIdempotencyConflictError
SDK rate limit errorRateLimitedError, with retryAfter from the SDK
SDK configuration errorAuthenticationError (acceptance: "notSubmitted")
SDK validation errorInvalidMessageError (acceptance: "notSubmitted")
SDK timeout errorTimeoutError
SDK connection errorTransportError
SDK unexpected response or redirectUnexpectedResponseError

Other API errors use their HTTP status with the shared mapping.