Docs
guides · sending

Batch & scheduled

Two ways to send beyond a single message: a batch of up to 100 in one round trip, and a future-dated send that the platform queues until its scheduledAt time.

01 Batch sends

POST/v1/emails/batch

Wrap up to 100 messages in an emails array. Each item is exactly the same shape as a single send — the same from, to, body, attachments, headers, and tags described in Send email. The batch is not all-or-nothing: each message is validated and queued independently.

import { DrinClient } from "@drin00/sdk";

const drin = new DrinClient({ apiKey: process.env.DRIN_API_KEY });

const { results } = await drin.emails.sendBatch({
  emails: [
    {
      from: { email: "hello@acme.com" },
      to: [{ email: "ada@example.com" }],
      subject: "Your invite",
      html: "<p>Welcome, Ada.</p>",
    },
    {
      from: { email: "hello@acme.com" },
      to: [{ email: "grace@example.com" }],
      subject: "Your invite",
      html: "<p>Welcome, Grace.</p>",
    },
  ],
});

for (const r of results) {
  if ("error" in r) console.error(r.index, r.error.message);
  else console.log(r.index, r.id, r.status);
}
sendMany shorthandWhen you already have an array of messages, emails.sendMany([…]) wraps it in the { emails } envelope for you — same result as sendBatch.
sendMany
// Convenience: pass a plain array, the SDK wraps it for you.
const { results } = await drin.emails.sendMany([
  { from: { email: "hello@acme.com" }, to: [{ email: "ada@example.com" }], subject: "Hi", html: "<p>1</p>" },
  { from: { email: "hello@acme.com" }, to: [{ email: "grace@example.com" }], subject: "Hi", html: "<p>2</p>" },
]);

02 The 207 multi-status response

A batch returns HTTP 207 Multi-Status, never a single success or failure. The body carries a results array with one entry per submitted message, in order, each tagged with its index:

  • Queued { index, id, status } where status is queued (or scheduled if that item carried a scheduledAt).
  • Failed{ index, error } with a typed error (e.g. a validation_error for a bad recipient). Other items in the same batch still succeed.
207 Multi-Status
{
  "results": [
    { "index": 0, "id": "msg_8f2c1a7e", "status": "queued" },
    {
      "index": 1,
      "error": { "type": "validation_error", "message": "to.0.email is invalid" }
    }
  ]
}
Always inspect every resultA 207 is not an all-success signal. Walk the results array and branch on whether each entry has an id or an error; don't assume the call either fully succeeded or fully failed.

03 Idempotency on batches

A batch is one HTTP request, so an Idempotency-Key covers the whole array. Replaying the same key within the window returns the original results instead of re-sending all 100 messages — which is exactly what you want when a network blip leaves you unsure whether the batch landed.

The key is per request, not per messageOne key guards the entire batch as a unit; you can't idempotency-key individual items inside it. To dedupe at the message level, send those messages one at a time, each with its own key. Full rules are in Idempotency & retries.

04 Scheduled sends

POST/v1/emails

To send in the future, add scheduledAt — an ISO-8601 timestamp — to any single send. The message is accepted immediately and comes back with status: "scheduled" instead of queued; the platform holds it and queues it for delivery at that time.

await drin.emails.send({
  from: { email: "hello@acme.com" },
  to: [{ email: "customer@example.com" }],
  subject: "Your trial ends tomorrow",
  html: "<p>Upgrade to keep your data.</p>",
  scheduledAt: "2026-07-01T09:00:00Z", // ISO-8601, UTC
});
// → { id: "msg_…", status: "scheduled" }
scheduledAtstringOptional
ISO-8601 timestamp for the future send (e.g. 2026-07-01T09:00:00Z). Use UTC, or include an explicit offset. A time in the past sends right away.
Schedule inside a batch tooscheduledAt is a per-message field, so items in a batch can each have their own send time — schedule some for later and queue others immediately, all in one request.
Suppression is checked at send timeRecipients are screened against your suppression list when a scheduled message actually goes out, not when you schedule it — so an address that bounces in the meantime is still protected.

05 Next