Effect SDK
Send email from Effect with Samva's Effect-native SDK entrypoint.
Effect SDK
Use the samva/effect
entrypoint when your app already uses Effect. It gives
you typed Samva errors, Effect.retry with Schedule, and a Layer-provided
client over the fetch HTTP transport.
Samva sends from the verified sender configured on your account, so the
Effect SDK examples do not include a from field.
Install
bun add samva effect@4.0.0-beta.85Keep SAMVA_API_KEY server-side only. The Effect SDK currently uses
effect/unstable/http, so pin a compatible Effect 4 beta while the namespace is
still pre-stable.
First send
Use createClient inside an Effect program and provide FetchHttpClient.layer
at the edge of the program:
import { Effect } from "effect";
import { FetchHttpClient } from "effect/unstable/http";
import { createClient } from "samva/effect";
const program = Effect.gen(function* () {
const samva = yield* createClient({ apiKey: process.env.SAMVA_API_KEY! });
return yield* samva.email.send({
to: "ada@example.com",
subject: "Welcome",
html: "<p>Hi Ada</p>",
text: "Hi Ada",
});
}).pipe(Effect.provide(FetchHttpClient.layer));
const message = await Effect.runPromise(program);
console.log(message.id, message.status);Layer-provided client
In a real app, build the layer once and read SamvaClient wherever you need to
send:
import { Effect } from "effect";
import { SamvaClient } from "samva/effect";
const SamvaLayer = SamvaClient.layerFetch({
apiKey: process.env.SAMVA_API_KEY!,
});
const sendWelcome = (to: string) =>
Effect.gen(function* () {
const samva = yield* SamvaClient;
return yield* samva.email.send({
to,
subject: "Welcome",
html: "<p>Your workspace is ready.</p>",
text: "Your workspace is ready.",
});
});
await Effect.runPromise(sendWelcome("ada@example.com").pipe(Effect.provide(SamvaLayer)));SamvaClient.layerFetch(config) uses globalThis.fetch. Use
SamvaClient.layer(config) if you want to provide a custom
HttpClient.HttpClient implementation.
Typed errors and retry
email.send is backed by the generated messages.send operation, so generated
error tags are operation-status tags such as MessagesSend429 and
MessagesSend422. Match those wrapper tags, then read the status-specific
cause.
import { Effect, Schedule } from "effect";
const retryableSend = sendWelcome("ada@example.com").pipe(
Effect.retry({
schedule: Schedule.exponential("200 millis").pipe(Schedule.jittered),
times: 3,
while: (error) =>
error._tag === "MessagesSend429" ||
error._tag === "MessagesSend500" ||
error._tag === "MessagesSend502",
}),
Effect.catchTags({
MessagesSend422: (error) =>
Effect.succeed({
status: 400,
fields: error.cause.fields ?? {},
}),
MessagesSend401: () =>
Effect.succeed({
status: 500,
message: "SAMVA_API_KEY is invalid or missing permissions.",
}),
MessagesSend429: (error) =>
Effect.succeed({
status: 429,
retryAfterSeconds:
typeof error.cause.retryAfterSeconds === "number" &&
Number.isFinite(error.cause.retryAfterSeconds)
? error.cause.retryAfterSeconds
: undefined,
}),
MessagesSend500: () =>
Effect.succeed({
status: 502,
message: "Samva returned a transient server error after retries.",
}),
MessagesSend502: () =>
Effect.succeed({
status: 502,
message: "Samva returned a transient gateway error after retries.",
}),
}),
);React Email and edge runtimes
Samva takes rendered html and optional text. If you use React Email, render
the component first and pass the strings to email.send; the
React Email integration covers templates and
previewing in more depth.
Because the Effect transport uses fetch, the same send path runs in Bun, Node with fetch, Vercel Edge, and Cloudflare Workers. Pass the API key from your server or edge environment binding, not from browser code.
Cookbook and example
The cookbook and example links resolve after the companion
samva-integrations PR lands.
If your app does not use Effect, use the standard TypeScript SDK instead.