Custom providers
Wrap any delivery service in an EmailAdapter and get the same validation, capability checks, receipts and spans as the built-in providers.
To support a provider the package does not ship, write an EmailAdapter and pass it to Email.make. Email.make returns an EmailService, and Layer.succeed(Email, service) turns it into a layer.
Example (An Adapter for a Hypothetical HTTP API)
import { , } from "effect";
import { , , } from "@effect-kit/emails";
import type { EmailAdapter } from "@effect-kit/emails";
const : EmailAdapter = {
: "acme",
: {
: true,
: true,
: true,
: true,
: false,
: false,
: "none",
: false,
: false,
: false,
: { : "unsupported" },
},
: () =>
.({
: async () => {
const = await ("https://api.acme.test/send", {
: "POST",
: .({
: ..,
: ..(() => .),
: .,
: .,
: .,
}),
});
const = (await .()) as { : string };
return {
: "acme",
: "accepted" as ,
: [{ : "providerMessageId" as , : . }],
};
},
: () =>
new ({
: "acme",
: "send",
: "unknown",
: new (),
}),
}),
};
export const = .(, .());Code that uses Email does not change. Provide AcmeLayer where you would provide a built-in provider layer.
What Email.make adds
The adapter only submits. Email.make wraps it so that every send, in this order:
- Validates the message and rejects unknown keys with
InvalidMessageError. - Checks the message against your
capabilitiesand fails withUnsupportedFeatureError. - Calls your
sendwith aNormalizedEmailMessageand the decodedSendOptions. - Validates the receipt you return.
- Records the
Email.sendspan described in Observability.
The adapter never sees a message that failed steps 1 or 2.
Adapter input
message is a NormalizedEmailMessage. Compared with what callers pass, from is always a Mailbox object, and to, cc, bcc and replyTo are always arrays of Mailbox. Attachment bytes are private copies.
capabilities must describe what your adapter honors, because step 2 trusts it. The schema also requires text or html to be true, requires text and html both true when htmlAndText is, and requires attachments when inlineAttachments is.
Failing and returning receipts
Return failures as EmailError and set acceptance truthfully. The rules in Errors and retries apply: use notSubmitted only when nothing left your process, rejected when the provider refused, and unknown otherwise.
Email.make fails a send with UnexpectedResponseError and acceptance: "unknown" if the receipt:
- does not decode as a
SendReceipt, - names a different
providerthan the adapter, - lists a recipient
indexthat is out of range for itsrole.
A SendReceipt with status partiallyAccepted must list at least one accepted and one rejected recipient. A receipt with status accepted must list none rejected.
Invalid configuration throws
Email.make throws a TypeError synchronously if provider or capabilities is invalid. A
provider name starts with a letter, digit or @ and contains only letters, digits and @ . _ / -, up to 128 characters.