Send a batch of messages

POST/api/v1/messages/batch
Requires anorganization API keywith the scopemessages:send

One shared template and a list of recipients, each with their own variables. Up to 500 recipients per call.

A partial failure is not an error. Every recipient is processed independently, so one suppressed or malformed address does not stop the rest — the call still returns 200 and the per-recipient outcome is in results. Check failed rather than the status code, or you will silently lose mail you believe you sent.

This is for transactional mail going to many people at once. For marketing sends to a list or segment, use campaigns instead: they add unsubscribe handling, review and reporting that this endpoint deliberately does not.

Parameters

NameInTypeDescription
Idempotency-KeyheaderstringSend a unique value — a UUID is ideal — to make this request safe to retry. If we have already answered a request with the same key and the same body, you get that exact response back with `Idempotent-Replayed: true` and nothing is sent a second time. Reusing a key with a DIFFERENT body is a 409, because answering the first response to a second message would silently swallow it. Keys are scoped to your organization and honoured for 24 hours. A 5xx does not record a key: we cannot say whether the message left, so your retry genuinely retries. Omit the header and nothing changes.

Request body

  • messagesarray<object>required
    • tostringrequired
    • variablesobject
  • fromstringrequired
  • subjectstring
  • htmlstring
  • textstring
  • template_idstring
  • tagsarray<string>

Request

POST/api/v1/messages/batch
curl -X POST 'https://app.mailyte.com/api/v1/messages/batch' \
  -H 'Authorization: Bearer mk_live_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "from": "hello@yourdomain.com",
    "subject": "Your weekly summary",
    "template_id": "01JBT8XQ2M9WYC3K4F6R7S8T9V",
    "messages": [
      {
        "to": "ada@example.com",
        "variables": {
          "first_name": "Ada"
        }
      },
      {
        "to": "grace@example.com",
        "variables": {
          "first_name": "Grace"
        }
      }
    ]
  }'
The key names its own organization, so no X-Organization-ID header is needed.

Response

The batch was processed. Individual recipients may still have failed — check `failed`.

  • dataobject
    • objectstringbatch
    • sentinteger

      Rows where `accepted` is true.

    • failedinteger

      Rows where `accepted` is false. A refused recipient is a row, never a failed request: `sent + failed` always equals `results` length.

    • resultsarray<object>

      One row per recipient, in the order submitted.

      • recipientstring

        Which recipient this row is about -- the address you submitted, echoed back so a row can be matched to a request entry without relying on position. NULL WOULD MEAN UNKNOWN and cannot happen: `messages[].to` is required and validated as an email before any row is built, and the row is built from that value on both the accepted and the refused path. A refused row still names its recipient -- that is the point of it -- so null here is never how a failure is reported. `error` is.

      • acceptedboolean

        The ONLY field to branch on. Every row carries every key, so a missing `id` never has to be read as a success.

      • idstring

        The bare ULID handle for this recipient's copy -- the same thing `POST /messages` calls `id`, resolving at `GET /email-logs/messages/{id}`. Null when `accepted` is false.

      • message_idstring

        The RFC 5322 Message-ID for this recipient's copy, angle brackets included, for `GET /domains/{domain}/messages/{messageId}`. A different string from `id`, for a different endpoint. Null when `accepted` is false.

      • errorstring

        Why this one recipient was refused -- a suppression, a malformed address. Null when `accepted` is true.

200application/json
{
  "message": "Batch processed",
  "code": 200,
  "success": true,
  "data": {
    "sent": 1,
    "failed": 1,
    "results": [
      {
        "to": "ada@example.com",
        "message_id": "<01JBT8XQ2M@yourdomain.com>"
      },
      {
        "to": "grace@example.com",
        "error": "Recipient is suppressed."
      }
    ]
  }
}

Returned inside the standard envelope.

Errors

StatusWhen
401The API key is missing, unknown, revoked or expired. All four answer identically, on purpose: distinguishing them would confirm which keys exist.
403The key is valid but may not do this: it lacks the required scope, its IP allowlist does not include you, or this endpoint does not accept API keys.
404No such resource in this organization.
422The request was understood but the values were not acceptable.
429Too many requests, or the organization has spent its sending allowance. `Retry-After` says how long to wait.

Every status, with what causes it and what to do, is on the error reference.