Skip to main content
The batch endpoint allows you to send up to 100 emails in a single API request, reducing overhead and improving throughput for high-volume sending.

When to Use Batch Sending

  • Sending the same notification to multiple users
  • Processing a queue of outbound emails
  • Migrating emails from another provider
  • Any scenario where you need to send more than a few emails at once

Basic Batch Request

from ark import Ark

client = Ark()

result = client.emails.send_batch(
    emails=[
        {
            "from_": "notifications@yourdomain.com",
            "to": ["user1@example.com"],
            "subject": "Your weekly digest",
            "html": "<p>Here is what happened this week...</p>"
        },
        {
            "from_": "notifications@yourdomain.com",
            "to": ["user2@example.com"],
            "subject": "Your weekly digest",
            "html": "<p>Here is what happened this week...</p>"
        }
    ]
)

print(f"Accepted: {result.data.accepted}, Failed: {result.data.failed}")

Response Format

The batch endpoint returns detailed results for each email:
{
  "success": true,
  "data": {
    "total": 100,
    "accepted": 98,
    "failed": 2,
    "messages": {
      "user1@example.com": {
        "id": 12345,
        "token": "abc123"
      },
      "user2@example.com": {
        "id": 12346,
        "token": "def456"
      },
      "invalid-email": {
        "error": "Invalid email format"
      }
    }
  }
}
Partial Success: Batch requests can return HTTP 207 when some emails succeed and others fail. Always check individual results.

Handling Partial Failures

result = client.emails.send_batch(emails=email_list)

accepted = []
failed = []

for recipient, info in result.data.messages.items():
    if "error" in info:
        failed.append((recipient, info["error"]))
    else:
        accepted.append((recipient, info))

if failed:
    print(f"Failed emails: {failed}")

print(f"Accepted: {len(accepted)}, Failed: {len(failed)}")

Chunking Large Lists

For lists larger than 100 emails, chunk them into batches:
def chunk(items, size):
    for i in range(0, len(items), size):
        yield items[i:i + size]

async def send_large_list(emails):
    results = []

    for batch in chunk(emails, 100):
        result = client.emails.send_batch(emails=batch)
        results.append(result)

    return results

# Usage
emails = generate_emails(1000)
results = await send_large_list(emails)

Personalization

Each email in a batch can have unique content:
users = get_users()

emails = [
    {
        "from_": "hello@yourdomain.com",
        "to": [user.email],
        "subject": f"Welcome, {user.first_name}!",
        "html": f"<h1>Hello {user.first_name}</h1><p>Thanks for joining us!</p>",
        "metadata": {"user_id": user.id}
    }
    for user in users
]

result = client.emails.send_batch(emails=emails)
Metadata in webhooks: Each email’s metadata is returned in webhook events, letting you correlate delivery events back to your users. See metadata validation rules for limits (10 keys, 40 char keys, 500 char values).

Using Tags for Tracking

Add tags to track batch performance:
import time

batch_id = f"weekly-digest-{int(time.time())}"

emails = [
    {
        "from_": "digest@yourdomain.com",
        "to": [user.email],
        "subject": "Your Weekly Digest",
        "html": generate_digest(user),
        "tag": batch_id  # Single tag for filtering this batch
    }
    for user in users
]

result = client.emails.send_batch(emails=emails)

# Later, filter emails by batch
batch_emails = client.emails.list(tag=batch_id)

Best Practices

For fewer than 10 emails, individual requests may be simpler and have similar overhead.
Check both the HTTP status and individual email results for comprehensive error handling.
Include idempotency keys to safely retry failed batches.
Track the ratio of accepted vs failed emails to identify systemic issues.

Limits

LimitValue
Emails per batch100
Request size10 MB
Recipients per email50

API Reference

View complete batch endpoint documentation