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

Amazon SES

Send email through Amazon SES with the @effect-kit/emails/ses Layer.

The @effect-kit/emails/ses subpath sends messages through Amazon SES and provides the Email service.

Installation

npm install @effect-kit/emails effect @aws-sdk/client-sesv2@3.1157.0

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

Setup

Ses.layerConfig() reads these variables:

VariableRequiredValue
AWS_REGIONyesAWS region such as eu-west-1

Credentials are not read by layerConfig. They come from the AWS SDK default credential chain.

Example (Sending a Message With Amazon SES)

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

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

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

.(.(.()));

Use a from address your Amazon SES account is allowed to send from.

Options

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

Prop

Type

Capabilities

Amazon SES 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

  • layerConfig reads only AWS_REGION. Credentials resolve through the AWS SDK default chain (environment, shared files, instance or task roles). To pass keys yourself, use layer with credentials.
  • The owned client is built with maxAttempts: 1, so the AWS SDK never retries a send. The Layer destroys it when its scope closes.
  • Messages go out as SES v2 Simple content. Attachments with a contentId are sent with ContentDisposition: INLINE.
  • The receipt's reference is the SES MessageId, and requestId comes from the SDK response metadata.

Using your own client

layerClient takes any object with send(command, { abortSignal }), which an SESv2Client has. options is optional and accepts timeout and configurationSetName.

Example (Borrowing a Amazon SES Client)

import { SESv2Client } from "@aws-sdk/client-sesv2";
import * as Ses from "@effect-kit/emails/ses";

const client = new SESv2Client({ region: "eu-west-1", maxAttempts: 1 });

const EmailLive = Ses.layerClient(client, { configurationSetName: "transactional" });

You own a borrowed client. The Layer never closes it, and the client keeps its own retry, timeout and logging policy. Set maxAttempts: 1 on a client you own if you want the same no-retry behavior as the owned client.

Errors

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

fieldRaised when
regionThe region is not lowercase letters, digits and hyphens.
timeoutThe timeout is not finite and positive.
endpointThe URL is not http or https, or has credentials, a query or a fragment.
configurationSetNameThe name does not match [A-Za-z0-9_-]{1,64}.
SES errorReason
TimeoutErrorTimeoutError
AbortError, ECONNRESET, ECONNREFUSED, EPIPE, ENOTFOUND, EAI_AGAINTransportError
CredentialsProviderError, TokenProviderError, InvalidClientTokenId, ExpiredToken, UnrecognizedClientExceptionAuthenticationError
TooManyRequestsExceptionRateLimitedError
LimitExceededExceptionQuotaExceededError
MessageRejectedMessageRejectedError
BadRequestExceptionInvalidMessageError
MailFromDomainNotVerifiedException, SendingPausedException, AccountSuspendedException, AccessDeniedExceptionPermissionDeniedError

The SES error name is kept as providerCode. CredentialsProviderError and TokenProviderError are acceptance: "notSubmitted" because no request was made. For other errors acceptance follows the HTTP status: rejected for a 4xx other than 409, unknown otherwise.

Any other HTTP status uses the shared mapping: 401 is AuthenticationError, 403 PermissionDeniedError, 429 RateLimitedError, 400 and 422 InvalidMessageError, 5xx ProviderUnavailableError, and anything else ProviderError. acceptance is rejected for a 4xx status other than 409 and unknown otherwise. See Errors.