# Batch sending

> Send up to 100 emails in a single request with POST /emails/batch.

Send multiple distinct emails in one request with `POST /emails/batch`. The body is a JSON array of email objects — each with the same shape as a single [POST /emails](https://www.sendping.co/docs/api/emails-send) body. A batch may contain **up to 100** emails.

## When to use batch sending

Reach for the batch endpoint when you need to send many distinct transactional emails at once — order confirmations, notifications, or per-recipient messages with unique content — and want to cut the number of API calls. For marketing email to an audience, use a [campaign](https://www.sendping.co/docs/audiences/overview) instead.

## How a batch is processed

SendPing validates **every** email in the batch *before* sending any of them. If a single item fails validation, the whole request is rejected with that item's error and nothing is sent — so an invalid item late in the array can never leave earlier items already delivered.

Once all items pass validation, a **small** batch is sent in order before the response returns (`200`), and a **large** one is queued and delivered by the background sender (`202`). Either way the response body carries `data` with one id per email, in request order. See [Batch delivery modes](https://www.sendping.co/docs/api/emails-batch-delivery) for the size boundary, what `202` adds to the body, and how to follow a queued batch to completion.

> **Note:** Each item is an independent email with its own `from`, `to`, `subject`, body, and headers. Suppressed recipients are skipped per-item, exactly as for a single send.

## Limitations

- A batch holds a maximum of **100 emails**; an empty array is also rejected.
- The `attachments` field is **not supported** in a batch — send those as single [POST /emails](https://www.sendping.co/docs/api/emails-send) requests. See [Attachments](https://www.sendping.co/docs/emails/attachments).
- The `scheduled_at` field is **not supported** in a batch — schedule those as single sends. See [Schedule email](https://www.sendping.co/docs/emails/schedule).
- A single invalid item (missing required field, invalid data) fails the whole request and nothing is sent.

> **Note:** You can make a batch send safe to retry by passing an `Idempotency-Key` header for the whole batch — see [Idempotency keys](https://www.sendping.co/docs/emails/idempotency). Choose a key that represents the batch (e.g. a team or job id).

## Send a batch

**Node.js**

```js
import { SendPing } from 'sendping';

const mb = new SendPing('mb_xxxxxxxxx');

const { data, error } = await mb.batch.send([
  {
    "from": "Acme <hello@yourdomain.com>",
    "to": ["delivered+first@test.sendping.co"],
    "subject": "Welcome, first user",
    "html": "<p>Hello!</p>"
  },
  {
    "from": "Acme <hello@yourdomain.com>",
    "to": ["delivered+second@test.sendping.co"],
    "subject": "Welcome, second user",
    "html": "<p>Hello!</p>"
  }
]);
console.log({ data, error });
```

**Ruby**

```ruby
require "sendping"

SendPing.api_key = "mb_xxxxxxxxx"

SendPing::Batch.send([
  {
    "from": "Acme <hello@yourdomain.com>",
    "to": [
      "delivered+first@test.sendping.co"
    ],
    "subject": "Welcome, first user",
    "html": "<p>Hello!</p>"
  },
  {
    "from": "Acme <hello@yourdomain.com>",
    "to": [
      "delivered+second@test.sendping.co"
    ],
    "subject": "Welcome, second user",
    "html": "<p>Hello!</p>"
  }
])
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

use SendPing\SendPing;

$sendping = SendPing::client('mb_xxxxxxxxx');

$sendping->batch->send([
  [
    'from' => "Acme <hello@yourdomain.com>",
    'to' => [
      "delivered+first@test.sendping.co"
    ],
    'subject' => "Welcome, first user",
    'html' => "<p>Hello!</p>"
  ],
  [
    'from' => "Acme <hello@yourdomain.com>",
    'to' => [
      "delivered+second@test.sendping.co"
    ],
    'subject' => "Welcome, second user",
    'html' => "<p>Hello!</p>"
  ]
]);
```

**Python**

```python
import sendping

sendping.api_key = "mb_xxxxxxxxx"

sendping.Batch.send([
  {
    "from": "Acme <hello@yourdomain.com>",
    "to": [
      "delivered+first@test.sendping.co"
    ],
    "subject": "Welcome, first user",
    "html": "<p>Hello!</p>"
  },
  {
    "from": "Acme <hello@yourdomain.com>",
    "to": [
      "delivered+second@test.sendping.co"
    ],
    "subject": "Welcome, second user",
    "html": "<p>Hello!</p>"
  }
])
```

**Go**

```go
import "github.com/shekhu10/sendping-sdks/sendping-go"

client := sendping.NewClient("mb_xxxxxxxxx")

batch, err := client.Batch.SendEmails([]*sendping.BatchEmailRequest{
    {
        From:    "Acme <hello@yourdomain.com>",
        To:      []string{"delivered+first@test.sendping.co"},
        Subject: "Welcome, first user",
        Html:    "<p>Hello!</p>",
    },
    {
        From:    "Acme <hello@yourdomain.com>",
        To:      []string{"delivered+second@test.sendping.co"},
        Subject: "Welcome, second user",
        Html:    "<p>Hello!</p>",
    },
})
```

**Rust**

```rust
use sendping::{BatchEmailOptions, SendPing};

let mb = SendPing::new("mb_xxxxxxxxx");

let items = vec![
    BatchEmailOptions::new(
        "Acme <hello@yourdomain.com>",
        ["delivered+first@test.sendping.co"],
        "Welcome, first user",
    )
    .with_html("<p>Hello!</p>"),
    BatchEmailOptions::new(
        "Acme <hello@yourdomain.com>",
        ["delivered+second@test.sendping.co"],
        "Welcome, second user",
    )
    .with_html("<p>Hello!</p>"),
];
let _batch = mb.batch.send_emails(items).await?;
```

**Java**

```java
import co.sendping.SendPing;
import co.sendping.SendPingResponse;
import co.sendping.requests.BatchEmailRequest;
import java.util.List;

SendPing sendping = new SendPing("mb_xxxxxxxxx");

List<BatchEmailRequest> requests = List.of(
        BatchEmailRequest.builder()
                .from("Acme <hello@yourdomain.com>")
                .to("delivered+first@test.sendping.co")
                .subject("Welcome, first user")
                .html("<p>Hello!</p>")
                .build(),
        BatchEmailRequest.builder()
                .from("Acme <hello@yourdomain.com>")
                .to("delivered+second@test.sendping.co")
                .subject("Welcome, second user")
                .html("<p>Hello!</p>")
                .build());

SendPingResponse response = sendping.batch().sendEmails(requests);
```

**.NET**

```csharp
using SendPing;

ISendPing sendping = SendPingClient.Create("mb_xxxxxxxxx");

var resp = await sendping.EmailBatchSendAsync(new List<BatchEmailMessage>
{
    new()
    {
        From = "Acme <hello@yourdomain.com>",
        To = "delivered+first@test.sendping.co",
        Subject = "Welcome, first user",
        HtmlBody = "<p>Hello!</p>",
    },
    new()
    {
        From = "Acme <hello@yourdomain.com>",
        To = "delivered+second@test.sendping.co",
        Subject = "Welcome, second user",
        HtmlBody = "<p>Hello!</p>",
    },
});
```

**cURL**

```bash
curl -X POST 'https://www.sendping.co/api/emails/batch' \
  -H 'Authorization: Bearer mb_xxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '[
  {
    "from": "Acme <hello@yourdomain.com>",
    "to": ["delivered+first@test.sendping.co"],
    "subject": "Welcome, first user",
    "html": "<p>Hello!</p>"
  },
  {
    "from": "Acme <hello@yourdomain.com>",
    "to": ["delivered+second@test.sendping.co"],
    "subject": "Welcome, second user",
    "html": "<p>Hello!</p>"
  }
]'
```

**CLI**

```bash
sendping emails batch \
  --data '[{"from":"Acme <hello@yourdomain.com>","to":["delivered+first@test.sendping.co"],"subject":"Welcome, first user","html":"<p>Hello!</p>"},{"from":"Acme <hello@yourdomain.com>","to":["delivered+second@test.sendping.co"],"subject":"Welcome, second user","html":"<p>Hello!</p>"}]'
```

## Response

The response is a `data` array of the created emails, each with its `id`, in the same order as the request.

```json
{
  "data": [
    { "id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794" },
    { "id": "1b2c3d4e-5f60-7a8b-9c0d-1e2f3a4b5c6d" }
  ]
}
```

> **Warning:** A batch larger than 100 emails, or an empty array, is rejected with a `validation_error`.
