Bulk audience send with chunking

Send to 10 000+ recipients efficiently. Sendoka's /api/v1/audiences/:id/send handles this natively — internally chunks by serialized byte count (~500 KB target) to stay under Neon's 1 MB statement cap.

Once — set up the audience

curl -X POST https://api.sendoka.com/api/v1/audiences \
  -H "Authorization: Bearer $SENDOKA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "newsletter-2026-04"}'

Response: { "id": "aud_01HN...", ... }.

Ingest contacts

Batch of up to 5 000 per call. Chunk your own side:

const headers = {
  Authorization: `Bearer ${process.env.SENDOKA_API_KEY}`,
  "Content-Type": "application/json",
};

const all = await readCsv("./subscribers.csv"); // [{email, name}, ...]
for (let i = 0; i < all.length; i += 5000) {
  await fetch("https://api.sendoka.com/api/v1/audiences/aud_01HN.../contacts", {
    method: "POST",
    headers,
    body: JSON.stringify({
      contacts: all.slice(i, i + 5000).map((c) => ({
        email: c.email,
        variables: { name: c.name },
      })),
    }),
  });
}

Send — via template

curl -X POST https://api.sendoka.com/api/v1/audiences/aud_01HN.../send \
  -H "Authorization: Bearer $SENDOKA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "news@yourdomain.com",
    "template": "newsletter",
    "subject": "April picks"
  }'

Response is immediate — audience send is queued and delivered by the background cron:

{ "audience_id": "aud_01HN...", "queued": 23417, "skipped_suppressed": 112, "status": "queued" }

Watch progress

curl https://api.sendoka.com/api/v1/audiences/aud_01HN.../status \
  -H "Authorization: Bearer $SENDOKA_API_KEY"
{
  "total": 23417,
  "sent": 18200,
  "delivered": 17901,
  "bounced": 194,
  "complained": 3,
  "opened": 8412,
  "clicked": 1203
}

Why you shouldn't DIY-chunk

Sendoka already:

  • Filters suppressed addresses before insert (org + tenant scoped).
  • Sizes each insert batch by Buffer.byteLength(JSON.stringify(row)) to stay under the Neon HTTP 1 MB statement cap.
  • Applies per-key + per-plan rate limits between batches.
  • Writes a single idempotency key per audience send so replays are safe.
  • Fires message.sent webhooks from after() so the HTTP request returns immediately.

If you send 10k rows in a loop via /api/v1/emails, you get 10k round-trips, 10k rate-limit checks, 10k webhook fan-outs. Don't.

Limits

  • Max 10 000 recipients in a single audience send — split larger lists.
  • Max 5 000 contacts per addContacts call.
  • Max 10 tags per message.