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

# Batch sending

> Queue up to 100 independent emails in one atomic API request

Use `POST /v1/emails/batch` when one job already contains several independent email requests.
A batch reduces HTTP overhead; it does not turn the items into one shared email.

## Request shape

Send an object with an `items` array.
Each item uses the normal send-email fields and may have its own recipient, sender, subject, content, schedule, tags, metadata, tracking settings, and idempotency key.

```json theme={"dark"}
{
  "items": [
    {
      "from": "billing@mail.company.com",
      "to": "alex@example.com",
      "subject": "Invoice 1042",
      "text": "Your invoice is ready.",
      "idempotencyKey": "invoice-1042-alex"
    },
    {
      "from": "billing@mail.company.com",
      "to": "sam@example.com",
      "subject": "Invoice 1043",
      "text": "Your invoice is ready.",
      "idempotencyKey": "invoice-1043-sam"
    }
  ]
}
```

## Limits and acceptance

* A request contains at most 100 items.
* The key needs `emails:send`.
* The batch endpoint currently allows 30 requests per 60 seconds per API key.
* Signal validates and queues the batch atomically: either every item is accepted or none is queued.
* After acceptance, each message delivers independently and can reach a different terminal status.

<Warning>
  Atomic queueing does not mean atomic delivery.
  One accepted message can deliver while another later bounces or fails.
</Warning>

## Correlate results

The response returns `results` in input order.
Each result includes its zero-based `index` and, when accepted, message identifiers and initial state.
Persist the mapping between your job item and the returned Signal ID.

## Retry safely

Give every item its own project-scoped `idempotencyKey`.
If the caller loses the response, submit the same logical items with the same keys.
Do not generate a new key for each retry because that creates new sends.

## When not to batch

Use individual sends when each message is produced by a separate job, when failures need independent HTTP retry timing, or when your queue already controls concurrency.
Use a topic or segment audience send when the intent is one audience operation rather than a collection of unrelated transactional messages.

## SDK example

After creating an authenticated client with the matching [SDK setup guide](/signal/send-with/sdk-overview),
use the operation for your language or framework.

Build `items` from normal send requests, then submit the collection in one call.

<CodeGroup dropdown>
  ```typescript TypeScript theme={"dark"}
  const batch = await signal.emails.batchSendEmails(
    { items },
    { headers: { Authorization: `Bearer ${process.env.SIGNAL_API_KEY}` } },
  );
  ```

  ```python Python theme={"dark"}
  batch = signal.emails.batch_send_emails(BatchSendRequest(items=items))
  ```

  ```go Go theme={"dark"}
  batch, err := client.Emails.BatchSendEmails(
      ctx,
      signal.BatchSendRequest{Items: items},
  )
  if err != nil {
      panic(err)
  }
  ```

  ```ruby Ruby theme={"dark"}
  request = ApolloDeploySignalSdk::BatchSendRequest.new(items: items)
  batch = signal.emails.batch_send_emails(body: request)
  ```

  ```ruby Rails theme={"dark"}
  request = ApolloDeploySignalSdkRails::BatchSendRequest.new(items: items)
  batch = ApolloDeploySignalSdkRails.rails_client.emails.batch_send_emails(
    body: request
  )
  ```

  ```php PHP / Laravel theme={"dark"}
  $request = new BatchSendRequest();
  $request->items = $items;
  $batch = $signal->emails()->batchSendEmails($request);
  ```

  ```java Java theme={"dark"}
  var request = new BatchSendRequest();
  request.items = items;
  var batch = signal.emails().batchSendEmails(request);
  ```

  ```kotlin Kotlin theme={"dark"}
  val batch = signal.emails.batchSendEmails(BatchSendRequest(items = items))
  ```

  ```csharp .NET theme={"dark"}
  var batch = await signal.Emails.BatchSendEmailsAsync(
      new BatchSendRequest { Items = items });
  ```

  ```rust Rust theme={"dark"}
  let batch = signal.emails
      .batch_send_emails(&BatchSendRequest { items })
      .await?;
  ```

  ```elixir Elixir theme={"dark"}
  request = %ApolloSignal.Types.BatchSendRequest{items: items}
  {:ok, batch, _metadata} = ApolloSignal.Client.batch_send_emails(client, request)
  ```

  ```swift Swift theme={"dark"}
  let batch = try await signal.emails.batchSendEmails(
      body: BatchSendRequest(items: items)
  )
  ```

  ```zig Zig theme={"dark"}
  var emails = client.emails();
  var result = try emails.batchSendEmails(.{ .items = items }, .{});
  defer result.deinit();

  switch (result) {
      .success => |_| {},
      .api_error => return error.SignalApiError,
  }
  ```
</CodeGroup>
