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

# Send emails in batch

> Queue multiple emails in a single API call with POST /api/v1/emails/batch

## Basic batch send

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.torpedo.co.mz/api/v1/emails/batch \
    -H "X-API-Key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "emails": [
        {
          "from": "Acme <hello@myapp.com>",
          "to": "alice@example.com",
          "subject": "Welcome to Acme",
          "html": "<h1>Welcome, Alice!</h1>"
        },
        {
          "from": "Acme <hello@myapp.com>",
          "to": "bob@example.com",
          "subject": "Welcome to Acme",
          "html": "<h1>Welcome, Bob!</h1>"
        }
      ]
    }'
  ```

  ```js Node.js theme={null}
  const res = await fetch('https://api.torpedo.co.mz/api/v1/emails/batch', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.TORPEDO_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      emails: [
        {
          from: 'Acme <hello@myapp.com>',
          to: 'alice@example.com',
          subject: 'Welcome to Acme',
          html: '<h1>Welcome, Alice!</h1>',
        },
        {
          from: 'Acme <hello@myapp.com>',
          to: 'bob@example.com',
          subject: 'Welcome to Acme',
          html: '<h1>Welcome, Bob!</h1>',
        },
      ],
    }),
  })
  const { data } = await res.json()
  // data is an array in the same order as the input
  // [{ id: '...', status: 'queued' }, { id: '...', status: 'queued' }]
  ```

  ```python Python theme={null}
  import httpx

  res = httpx.post(
      'https://api.torpedo.co.mz/api/v1/emails/batch',
      headers={'X-API-Key': TORPEDO_API_KEY},
      json={
          'emails': [
              {
                  'from': 'Acme <hello@myapp.com>',
                  'to': 'alice@example.com',
                  'subject': 'Welcome to Acme',
                  'html': '<h1>Welcome, Alice!</h1>',
              },
              {
                  'from': 'Acme <hello@myapp.com>',
                  'to': 'bob@example.com',
                  'subject': 'Welcome to Acme',
                  'html': '<h1>Welcome, Bob!</h1>',
              },
          ]
      },
  )
  data = res.json()['data']  # list of { id, status }
  ```

  ```go Go theme={null}
  package main

  import (
  	"bytes"
  	"encoding/json"
  	"fmt"
  	"net/http"
  	"os"
  )

  func main() {
  	payload, _ := json.Marshal(map[string]any{
  		"emails": []map[string]string{
  			{"from": "Acme <hello@myapp.com>", "to": "alice@example.com",
  				"subject": "Welcome to Acme", "html": "<h1>Welcome, Alice!</h1>"},
  			{"from": "Acme <hello@myapp.com>", "to": "bob@example.com",
  				"subject": "Welcome to Acme", "html": "<h1>Welcome, Bob!</h1>"},
  		},
  	})

  	req, _ := http.NewRequest("POST", "https://api.torpedo.co.mz/api/v1/emails/batch", bytes.NewBuffer(payload))
  	req.Header.Set("X-API-Key", os.Getenv("TORPEDO_API_KEY"))
  	req.Header.Set("Content-Type", "application/json")

  	resp, _ := http.DefaultClient.Do(req)
  	defer resp.Body.Close()
  	fmt.Println(resp.Status)
  }
  ```

  ```java Java theme={null}
  import java.net.http.*;
  import java.net.URI;

  var body = """
      {"emails":[
        {"from":"Acme <hello@myapp.com>","to":"alice@example.com",
         "subject":"Welcome","html":"<h1>Welcome, Alice!</h1>"},
        {"from":"Acme <hello@myapp.com>","to":"bob@example.com",
         "subject":"Welcome","html":"<h1>Welcome, Bob!</h1>"}
      ]}""";

  var request = HttpRequest.newBuilder()
      .uri(URI.create("https://api.torpedo.co.mz/api/v1/emails/batch"))
      .header("X-API-Key", System.getenv("TORPEDO_API_KEY"))
      .header("Content-Type", "application/json")
      .POST(HttpRequest.BodyPublishers.ofString(body))
      .build();

  var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
  System.out.println(response.body());
  ```

  ```csharp C# theme={null}
  using var client = new HttpClient();
  client.DefaultRequestHeaders.Add("X-API-Key", Environment.GetEnvironmentVariable("TORPEDO_API_KEY"));

  var payload = new {
      emails = new[] {
          new { from = "Acme <hello@myapp.com>", to = "alice@example.com",
                subject = "Welcome to Acme", html = "<h1>Welcome, Alice!</h1>" },
          new { from = "Acme <hello@myapp.com>", to = "bob@example.com",
                subject = "Welcome to Acme", html = "<h1>Welcome, Bob!</h1>" },
      }
  };

  var response = await client.PostAsJsonAsync("https://api.torpedo.co.mz/api/v1/emails/batch", payload);
  var result = await response.Content.ReadAsStringAsync();
  Console.WriteLine(result);
  ```
</CodeGroup>

Response:

```json theme={null}
{
  "data": [
    { "id": "01960000-0000-0000-0000-000000000001", "status": "queued" },
    { "id": "01960000-0000-0000-0000-000000000002", "status": "queued" }
  ]
}
```

The response array is in the **same order** as your input. The response is `202 Accepted`.

***

## Batch size limits

The maximum number of emails per batch request depends on your plan:

| Plan    | Max per batch |
| ------- | ------------- |
| Launch  | 50            |
| Starter | 100           |
| Growth  | 500           |
| Scale   | 1,000         |

Exceeding the limit returns `422`:

```json theme={null}
{
  "errors": [{ "message": "Your plan allows a maximum of 100 emails per batch request" }]
}
```

***

## All-or-nothing validation

The entire batch is validated before any email is queued. If **any** email fails a check, the whole request is rejected with `422` and nothing is sent.

Checks that apply to every email in the batch:

* `from` domain must be verified in your workspace
* Each email must include `html`, `text`, or both
* `to` must be a valid email address
* Recipient must not be on the suppression list

***

## Quota

Daily and monthly sending limits apply to the total number of emails in the batch. If the batch would push you over either limit, the entire request is rejected:

```json theme={null}
{
  "errors": [{ "message": "Daily email limit reached. Limit resets at midnight UTC." }]
}
```

Check current usage via `GET /api/v1/workspace/usage`.

***

## Domain-scoped keys

If your API key is scoped to a specific domain, every email in the batch must use that domain as the sender. A mismatch returns `403`.

***

## Idempotency

Batch requests do not support the `Idempotency-Key` header. Use individual `POST /api/v1/emails` requests if you need idempotent sends.
