Simulated provider: message #4 is rate limited.
const outcomes = yield* Email.sendMany(
orders.map((order) => ({ message: receipt(order) })),
{ concurrency: 3 },
);
// ReadonlyArray<Result<SendReceipt, EmailError>>, in input order
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.
One Email service for 16 providers. Every message is checked against the provider before it is sent.
Before it leaves
An attachment through MailerSend fails with UnsupportedFeatureError. No request is made.
01Providers
Pick a provider. The import and the Layer change; the table shows what it supports.
import { Effect } from "effect";
import { Email } from "@effect-kit/emails";
import * as Resend from "@effect-kit/emails/resend";
const program = Effect.gen(function* () {
const email = yield* Email;
return yield* email.send(welcome);
});
program.pipe(Effect.provide(Resend.layerConfig()));
| Provider | Text | HTML | Both bodies | Sender name | CC | BCC | Reply-to | Headers | Attachments | Inline images | Idempotency |
|---|---|---|---|---|---|---|---|---|---|---|---|
| yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes, replays | |
| yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no | |
| yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no | |
| yes | yes | yes | yes | yes | yes | partly, one address | yes | yes | yes | no | |
| yes | yes | yes | yes | yes | yes | yes | yes | yes | no | no | |
| yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no | |
| yes | yes | yes | no | yes | yes | yes | yes | yes | yes | no | |
| yes | yes | yes | yes | yes | yes | partly, one address | yes | no | no | no | |
| yes | yes | yes | yes | yes | yes | partly, one address | yes | no | no | yes, rejects duplicates | |
| yes | yes | yes | yes | yes | yes | partly, one address | yes | yes | yes | no | |
| yes | yes | yes | yes | yes | yes | yes | yes | yes | no | no | |
| yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no | |
| yes | yes | yes | yes | yes | yes | yes | yes | yes | no | no | |
| yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes, replays | |
| yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes, replays | |
| no | yes | no | yes | no | no | no | no | no | no | no |
02Playground
Simulated provider: message #4 is rate limited.
const outcomes = yield* Email.sendMany(
orders.map((order) => ({ message: receipt(order) })),
{ concurrency: 3 },
);
// ReadonlyArray<Result<SendReceipt, EmailError>>, in input order
Inspired by Kit Langton's Visual Effect
03Idempotency
Pass an idempotency key. Each provider handles the repeat its own way, and you get a typed answer.
email.send(welcome, { idempotencyKey: "welcome/ada" })
Resend: keys kept 24 hours
04React templates
EmailReact.render turns a React Email component into the HTML body and the plain-text body. Change the name to see both update.
import * as EmailReact from "@effect-kit/emails/react";
const Welcome = ({ name }: { name: string }) => (
<Html>
<Heading>Welcome, {name}</Heading>
<Text>Your account is ready. Sign in to finish setting up your workspace.</Text>
<Button href="https://example.com/start">Open dashboard</Button>
</Html>
);
const content = yield* EmailReact.render(<Welcome name="Ada" />); // { html, text }
yield* email.send({ from, to, subject: "Welcome", ...content });from hello@example.com
subject Welcome
Welcome, Ada
Your account is ready. Sign in to finish setting up your workspace.
Open dashboardSent with @effect-kit/emails
Get started
pnpm add @effect-kit/emails effect resend