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
/v1/emails/batchWrap 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);
}emails.sendMany([…]) wraps it in the { emails } envelope for you — same result as sendBatch.// 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 }wherestatusisqueued(orscheduledif that item carried ascheduledAt). - Failed —
{ index, error }with a typederror(e.g. avalidation_errorfor a bad recipient). Other items in the same batch still succeed.
{
"results": [
{ "index": 0, "id": "msg_8f2c1a7e", "status": "queued" },
{
"index": 1,
"error": { "type": "validation_error", "message": "to.0.email is invalid" }
}
]
}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.
04 Scheduled sends
/v1/emailsTo 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" }2026-07-01T09:00:00Z). Use UTC, or include an explicit offset. A time in the past sends right away.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.