Brevo
Send email through Brevo with the @effect-kit/emails/brevo Layer.
The @effect-kit/emails/brevo subpath sends messages through Brevo and provides the Email service.
Installation
npm install @effect-kit/emails effect @getbrevo/brevo@6.2.0The SDK is a pinned optional peer. It is needed only when you import this subpath.
Setup
Brevo.layerConfig() reads these variables:
| Variable | Required | Value |
|---|---|---|
BREVO_API_KEY | yes | API key |
Example (Sending a Message With Brevo)
import { , , } from "effect";
import { } from "@effect-kit/emails";
import * as from "@effect-kit/emails/brevo";
const = .().(
.(.(.())),
);
const = .(function* () {
const = yield* ;
return yield* .({
: "hello@example.com",
: "ada@example.com",
: "Welcome",
: "Your account is ready.",
});
});
.(.(.()));Use a from address your Brevo account is allowed to send from.
Options
Brevo.layer takes these options. layerConfig builds the same object from a Config.
Prop
Type
Capabilities
Brevo 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 | one | yes | no | no | reject, 30 minutes |
These fail with UnsupportedFeatureError and acceptance: "notSubmitted" before any request:
- More than one
replyToaddress (featuremultipleReplyTo). - Any attachment (feature
attachments).
Idempotency
Mode reject, with a retention of 30 minutes. A repeated key is refused rather than replayed.
The key must be a UUID, otherwise the send fails with InvalidMessageError (path idempotencyKey) before sending. The package sends it to Brevo as a header named idempotencyKey, so a custom header with that name (any case) fails with InvalidMessageError (path headers).
When Brevo answers duplicate_parameter, the send fails with IdempotencyConflictError and acceptance: "unknown".
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.",
},
{ : "3f2504e0-4f89-41d3-9a0c-0305e82c3301" },
);
});Provider notes
- Brevo takes one reply-to address and no attachments through the shared API. Both fail before sending: a second
replyToaddress with featuremultipleReplyTo, an attachment with featureattachments. - Retries are off on the owned client (
maxRetries: 0).
Using your own client
layerClient takes any object with transactionalEmails.sendTransacEmail(message, options), which a BrevoClient has. options is optional.
Example (Borrowing a Brevo Client)
import { BrevoClient } from "@getbrevo/brevo";
import * as Brevo from "@effect-kit/emails/brevo";
const client = new BrevoClient({ apiKey: "fake-key", maxRetries: 0 });
const EmailLive = Brevo.layerClient(client);You own a borrowed client. The Layer never closes it, and the client keeps its own retry, timeout and logging policy. The package calls the client with maxRetries: 0 per request, so a borrowed client does not retry sends either.
Errors
ProviderConfigurationError carries provider: "brevo" 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 API key is not printable ASCII without spaces. |
| Brevo error code | Reason |
|---|---|
duplicate_parameter | IdempotencyConflictError (acceptance: "unknown") |
not_enough_credits, insufficient_credits, quota_exceeded | QuotaExceededError |
permission_denied | PermissionDeniedError |
recipient_blacklisted, recipient_rejected | MessageRejectedError |
| SDK timeout error | TimeoutError |
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. A network failure with no status is TransportError, and ETIMEDOUT or ECONNABORTED is TimeoutError, both with acceptance: "unknown".