> ## Documentation Index
> Fetch the complete documentation index at: https://docs.drin.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Batch & scheduled

> Send up to 100 messages in one request and read the 207 multi-status result, or schedule a single send for a future time with scheduledAt.

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.

## 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](/sending). The batch is not all-or-nothing: each message is validated and queued independently.

<CodeGroup>
  ```typescript Node.js theme={null}
  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);
  }
  ```

  ```bash cURL theme={null}
  curl https://api.drin.run/v1/emails/batch \
    -H "Authorization: Bearer $DRIN_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "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>"
        }
      ]
    }'
  ```
</CodeGroup>

<Tip>
  **sendMany shorthand.** When you already have an array of messages, `emails.sendMany([…])` wraps it in the `{ emails }` envelope for you — same result as `sendBatch`.
</Tip>

```typescript sendMany theme={null}
// 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>" },
]);
```

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

```json 207 Multi-Status theme={null}
{
  "results": [
    { "index": 0, "id": "msg_8f2c1a7e", "status": "queued" },
    {
      "index": 1,
      "error": { "type": "validation_error", "message": "to.0.email is invalid" }
    }
  ]
}
```

<Warning>
  **Always inspect every result.** A `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.
</Warning>

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

<Note>
  **The key is per request, not per message.** One 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](/idempotency).
</Note>

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

<CodeGroup>
  ```typescript Node.js theme={null}
  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" }
  ```

  ```bash cURL theme={null}
  curl https://api.drin.run/v1/emails \
    -H "Authorization: Bearer $DRIN_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "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"
    }'
  ```
</CodeGroup>

<ParamField body="scheduledAt" type="string">
  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.
</ParamField>

<Tip>
  **Schedule inside a batch too.** `scheduledAt` 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.
</Tip>

<Note>
  **Suppression is checked at send time.** Recipients are screened against your [suppression list](/suppressions) when a scheduled message actually goes out, not when you schedule it — so an address that bounces in the meantime is still protected.
</Note>

## Next

<CardGroup cols={2}>
  <Card title="Idempotency & retries" icon="shield" href="/idempotency">
    Make batches and single sends safe to retry.
  </Card>

  <Card title="Templates" icon="layer-group" href="/templates">
    Reuse one body across every message in a batch.
  </Card>

  <Card title="Send batch API" icon="code" href="/api-reference/emails/batch">
    The full `POST /v1/emails/batch` contract.
  </Card>

  <Card title="Metrics" icon="chart-line" href="/metrics">
    Watch a batch's delivery, bounce, and open rates.
  </Card>
</CardGroup>
