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.0The SDK is a pinned optional peer. It is needed only when you import this subpath.
Setup
Lettermint.layerConfig() reads these variables:
| Variable | Required | Value |
|---|---|---|
LETTERMINT_SENDING_TOKEN | yes | Sending 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:
| Text | HTML | Both | Sender name | Cc | Bcc | Reply-to | Headers | Attachments | Inline images | Idempotency |
|---|---|---|---|---|---|---|---|---|---|---|
| yes | yes | yes | yes | yes | yes | multiple | yes | yes | yes | replay, 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: truethe provider name becomeslettermint/sandbox, and a response that does not report the sandbox flag fails withUnexpectedResponseError. Without it, a response that reports the flag fails the same way. - Inline images need a fully qualified content id. A
contentIdwithout@fails withInvalidMessageError(pathattachments) before sending, because Lettermint would otherwise append@lmand 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.
field | Raised when |
|---|---|
timeout | The timeout is not finite and positive. |
token | The sending token is not printable ASCII without spaces. |
sendingToken | The SDK client could not be created from the token. |
| Lettermint failure | Reason |
|---|---|
| SDK conflict error | IdempotencyConflictError |
| SDK rate limit error | RateLimitedError, with retryAfter from the SDK |
| SDK configuration error | AuthenticationError (acceptance: "notSubmitted") |
| SDK validation error | InvalidMessageError (acceptance: "notSubmitted") |
| SDK timeout error | TimeoutError |
| SDK connection error | TransportError |
| SDK unexpected response or redirect | UnexpectedResponseError |
Other API errors use their HTTP status with the shared mapping.