Email

Schedule an email

Schedule one email for an absolute future instant, then inspect or cancel it before dispatch begins.

Use a scheduled message when the recipients and content are already known and the email should send later. This is a separate resource from an immediate send. messages.send has no scheduling field.

Choose the time and content

scheduledFor must be a future absolute ISO-8601 instant with Z or a numeric offset, for example 2026-08-01T09:00:00Z or 2026-08-01T14:30:00+05:30. A local date-time without an offset is rejected.

timezone is optional validated IANA metadata for display and audit. It never converts or reinterprets scheduledFor.

Choose exactly one content source:

  • Inline email requires subject and at least one of html or text.
  • Template email references exactly one published template by templateSlug or templateId, with optional templateData. Omit inline subject, html, and text. Add locale and timeZone to format the template's dates, numbers, and money for the recipient; see Format dates and money for the recipient.

Add headers on send.email to copy custom headers onto the email. See Set custom headers.

Use the Promise SDK

schedule-email.ts
import { createClient } from "samva";

const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });
const scheduled = await samva.scheduledMessages.create({
  send: {
    to: [{ email: "ada@example.com" }],
    channel: "email",
    email: {
      subject: "Your appointment is tomorrow",
      html: "<p>We will see you at 9:00.</p>",
      text: "We will see you at 9:00.",
    },
  },
  scheduledFor: "2026-08-01T09:00:00Z",
  timezone: "America/New_York",
}, { idempotencyKey: "appointment-ada-2026-08-01" });

await samva.scheduledMessages.cancel({ id: scheduled.id });

Use the Effect SDK

schedule-email-effect.ts
import { Effect } from "effect";
import * as Client from "samva/effect/client";
import * as ScheduledMessages from "samva/effect/scheduled-messages";

const program = Effect.gen(function* () {
  const scheduled = yield* ScheduledMessages.create({
    send: {
      to: [{ email: "ada@example.com" }],
      channel: "email",
      email: { subject: "Reminder", html: "<p>See you tomorrow.</p>" },
    },
    scheduledFor: new Date("2026-08-01T09:00:00Z"),
    timezone: "America/New_York",
  }, { idempotencyKey: "reminder-ada-2026-08-01" });

  return yield* ScheduledMessages.get({ id: scheduled.id });
}).pipe(Effect.provide(Client.layerFetch({ apiKey: process.env.SAMVA_API_KEY! })));

await Effect.runPromise(program);

Use the REST API

curl -X POST https://api.samva.dev/v1/messages/scheduled \
  -H "X-API-Key: $SAMVA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: reminder-ada-2026-08-01" \
  -d '{
    "send": {
      "to": [{ "email": "ada@example.com" }],
      "channel": "email",
      "email": { "subject": "Reminder", "html": "<p>See you tomorrow.</p>" }
    },
    "scheduledFor": "2026-08-01T09:00:00Z",
    "timezone": "America/New_York"
  }'

Use GET /v1/messages/scheduled/{id} to inspect the schedule and POST /v1/messages/scheduled/{id}/cancel to cancel it. See the generated scheduled messages reference for schemas and response fields.

Use the CLI

samva scheduled-messages create --to ada@example.com \
  --subject "Reminder" --html "<p>See you tomorrow.</p>" \
  --at 2026-08-01T09:00:00Z --timezone America/New_York \
  --idempotency-key reminder-ada-2026-08-01
samva scheduled-messages get <scheduled-message-id>
samva scheduled-messages cancel <scheduled-message-id> --yes

Use --template-slug <slug> --template-data '<json>' instead of the inline content flags to send a published template. samva scheduled-messages create --dry-run validates and prints the request without calling the API. This command does not take --header. Set headers inside send.email on the API payload. samva email send --header applies to an immediate send.

Use MCP

Ask the agent to call scheduled_messages_schedule_email with to, one valid content source, scheduledFor, and optional headers. It can inspect the result with scheduled_messages_get or scheduled_messages_list, cancel it with scheduled_messages_cancel, or explicitly resume a review-paused email with scheduled_messages_resume.

Resume after email review

If review is no longer clear when dispatch begins, the scheduled message pauses before anything is sent. It does not send automatically after approval. Resume it explicitly and choose what an overdue schedule should do:

resume-scheduled-email.ts
await samva.scheduledMessages.resume({ id: scheduled.id, body: { choice: "send_now" } });
samva scheduled-messages resume <scheduled-message-id> --send-now
samva scheduled-messages resume <scheduled-message-id> --at 2026-10-01T09:00:00Z

The request body is { choice: "send_now" } or { choice: "new_time", scheduledFor }, sent to POST /v1/messages/scheduled/{id}/resume. A new time must be a future absolute instant.

Cancellation, errors, and usage

A scheduled email can be cancelled only while its status is pending. Once dispatch begins, cancellation returns a conflict. At dispatch, it follows the normal email send path and usage semantics. Validation failures do not create a schedule; provider and delivery outcomes appear on the resulting message after dispatch. Handle authentication, validation, not-found, conflict, and usage-limit errors as described in the error reference.

Related documentation

On this page