Samva is in early access — self-serve signup is limited. Have a team invite? Sign up with that email. Contact us for access.

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.85

Keep 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.

On this page