Email

Send an email campaign

Create an email campaign for an audience, schedule a run, and inspect or control delivery progress.

Use a campaign for a named broadcast whose recipients come from contact IDs or tags. Creating a campaign sends nothing. A run snapshots the definition and starts immediately or at a future time.

Define the campaign

Campaigns are email-only and support at most 25,000 resolved recipients per run. Choose exactly one content source:

  • Inline email requires subject and html; text is an optional fallback.
  • Template email references exactly one published template by templateSlug or templateId, with optional templateData. Do not mix it with inline subject, html, or text.

The audience must use includeTags or contactIds as its source. excludeTags removes matches. Contacts resolve when the run starts. After the first run is created, the campaign definition is frozen and updates return a conflict.

Use the Promise SDK

send-campaign.ts
import { createClient } from "samva";

const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });
const campaign = await samva.campaigns.create({
  name: "August newsletter",
  channel: "email",
  content: {
    channel: "email",
    email: { subject: "What's new", html: "<h1>August updates</h1>" },
  },
  audience: { includeTags: ["newsletter"], excludeTags: ["bounced"] },
});

if (campaign.error) throw new Error("Could not create campaign");

const run = await samva.campaigns.scheduleRun({
  id: campaign.data.id,
  scheduledFor: "2026-08-01T09:00:00Z",
  idempotencyKey: "august-newsletter-v1",
});

if (run.error) throw new Error("Could not schedule campaign");

Use the Effect SDK

send-campaign-effect.ts
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! });
  const campaign = yield* samva.campaigns.create({
    payload: {
      name: "August newsletter",
      channel: "email",
      content: {
        channel: "email",
        email: { subject: "What's new", html: "<h1>August updates</h1>" },
      },
      audience: { includeTags: ["newsletter"] },
    },
  });

  return yield* samva.campaigns.scheduleRun(campaign.id, {
    payload: {
      scheduledFor: "2026-08-01T09:00:00Z",
      idempotencyKey: "august-newsletter-v1",
    },
  });
}).pipe(Effect.provide(FetchHttpClient.layer));

await Effect.runPromise(program);

Use the REST API

curl -X POST https://api.samva.app/v1/campaigns \
  -H "X-API-Key: $SAMVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "August newsletter",
    "channel": "email",
    "content": {
      "channel": "email",
      "email": { "subject": "What is new", "html": "<h1>August updates</h1>" }
    },
    "audience": { "includeTags": ["newsletter"] }
  }'

curl -X POST https://api.samva.app/v1/campaigns/<campaign-id>/runs \
  -H "X-API-Key: $SAMVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scheduledFor": "2026-08-01T09:00:00Z",
    "idempotencyKey": "august-newsletter-v1"
  }'

Omit scheduledFor to start immediately. When present, it must be an absolute ISO-8601 instant with Z or a numeric offset. Campaign runs do not accept timezone metadata. See the generated campaigns reference for every operation.

Use the CLI

samva campaigns create --name "August newsletter" \
  --subject "What's new" --html "<h1>August updates</h1>" \
  --include-tags newsletter
samva campaigns runs schedule <campaign-id> --at 2026-08-01T09:00:00Z \
  --idempotency-key august-newsletter-v1
samva campaigns runs get <campaign-id> <run-id>
samva campaigns runs recipients <campaign-id> <run-id> --status failed

Use samva campaigns create --dry-run to validate the definition without creating it. Use samva campaigns runs pause, resume, or cancel with the campaign ID and run ID to control a run.

Use MCP

Call samva_campaigns_create, then samva_campaigns_schedule_run. Track progress with samva_campaigns_get_run and inspect outcomes with samva_campaigns_list_recipients. The samva_campaigns_control_run tool accepts pause, resume, or cancel.

Idempotency, controls, errors, and usage

Pass an idempotencyKey when scheduling a run. It is scoped to the organization, and replaying the same key returns the original run. Pausing stops new dispatch and resuming safely re-enqueues unsettled recipients. Cancelling a future scheduled run is terminal. Cancellation after dispatch begins is best effort, so already claimed or provider-submitted deliveries may finish.

Each recipient follows the normal email send and usage path when dispatched. Run totals distinguish dispatched, failed, and skipped recipients; recipient records carry the outcome. Invalid state transitions and edits after the first run return conflicts. See the error reference for shared API error handling.

On this page