MailLandDocs

Batch Sending

Send emails to multiple recipients in a single API call

Batch sending is only available with Marketing stream credentials. Transactional credentials return 400 BATCH_NOT_ALLOWED_FOR_TRANSACTIONAL. This prevents misuse of transactional streams for bulk email.

Send a Batch

POST https://api.mail.land/v1/transactional/send-batch
Send a batch email
curl -X POST https://api.mail.land/v1/transactional/send-batch \
  -H "X-API-Key: ml_pem_Ab3Kx9mN2pQ7rT4wL8yD..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": {
      "email": "[email protected]",
      "name": "Your Company"
    },
    "recipients": [
      {
        "email": "[email protected]",
        "name": "Alice",
        "variables": { "firstName": "Alice", "plan": "Pro" }
      },
      {
        "email": "[email protected]",
        "name": "Bob",
        "variables": { "firstName": "Bob", "plan": "Starter" }
      }
    ],
    "subject": "Hi {{firstName}}, check out what'\''s new",
    "htmlContent": "<h1>Hey {{firstName}}!</h1><p>Your {{plan}} plan just got an upgrade.</p>",
    "variables": {
      "companyName": "Acme Inc",
      "year": "2026"
    },
    "tags": ["product-update", "february-2026"],
    "batchName": "February Product Update"
  }'

Request Fields

FieldTypeRequiredDescription
fromobjectYesSender address (email and optional name)
recipientsarrayYesList of recipients (note: recipients, not to)
recipients[].emailstringYesRecipient email
recipients[].namestringNoRecipient display name
recipients[].variablesobjectNoPer-recipient template variables
subjectstringConditionalRequired unless using templateId
templateIdstringNoUse a stored template
htmlContentstringConditionalHTML body (required if no templateId)
textContentstringNoPlain text fallback
variablesobjectNoGlobal variables. Applied whether or not templateId is set; on a key overlap, the recipient's own recipients[].variables wins
replyTostringNoReply-to address
batchNamestringNoHuman-readable name for this batch
tagsarrayNoTags for analytics and webhook filtering
customHeadersobjectNoCustom email headers
trackingobjectNoOpen/click tracking overrides
ipPoolIdstringNoReserved for dedicated IP pools. Not yet available: sending it returns 400 IP_POOL_NOT_FOUND. Omit it.
scheduledAtdatetimeNoSchedule for future delivery, up to 7 days ahead
externalBatchIdstringNoYour system's batch identifier: when supplied, it is echoed back as the response's batchId
priorityenumNoLow, Normal (default), High, Critical
idempotencyKeystringNoPrevent duplicate sends for the same key. Prefer the X-Idempotency-Key header

Note the field name difference: batch sending uses recipients while single send uses to.

Response

Status: 202 Accepted

Batch response
{
  "success": true,
  "batchId": "batch_f47ac10b58cc43729876fedcba098765",
  "messageIds": ["m_a1b2c3...", "m_d4e5f6..."],
  "totalRecipients": 2,
  "acceptedCount": 2,
  "rejectedCount": 0,
  "rejectedRecipients": [],
  "queuedAt": "2026-02-17T10:30:00Z",
  "estimatedCompletion": "2026-02-17T10:35:00Z",
  "isSandboxed": false
}
FieldTypeDescription
batchIdstringUnique batch identifier
messageIdsarrayPer-recipient message IDs (prefixed m_ for marketing)
totalRecipientsintTotal recipients in the request
acceptedCountintRecipients accepted for delivery
rejectedCountintRecipients rejected
rejectedRecipientsarrayDetails of rejected recipients with reasons
estimatedCompletiondatetimeEstimated time for batch processing completion

Variable Merging

Per-recipient variables override global variables when keys overlap:

Variable precedence
{
  "variables": {
    "companyName": "Acme Inc",
    "greeting": "Hello"
  },
  "recipients": [
    {
      "email": "[email protected]",
      "variables": { "greeting": "Dear valued customer" }
    },
    {
      "email": "[email protected]"
    }
  ]
}

Error Handling

Batch-specific errors:

StatusCodeDescription
400BATCH_NOT_ALLOWED_FOR_TRANSACTIONALTransactional credential cannot send batches
400VALIDATION_FAILEDExceeded 10,000 recipients per batch
400INVALID_REQUESTscheduledAt more than 7 days ahead ("Cannot schedule email more than 7 days in advance")
400TEMPLATE_NOT_FOUNDtemplateId names no active template of this account (also returned for a deleted template)
400IP_POOL_NOT_FOUNDipPoolId names no pool assigned to this account
422TEMPLATE_VARIABLE_MISSINGEvery recipient lacks a value for a {{placeholder}} (if only some do, they are skipped and listed in rejectedRecipients with the reason template_variable_missing: name). Nothing is queued
400BATCH_SEND_FAILEDAll recipients are suppressed
429RATE_LIMIT_EXCEEDEDRate limit exceeded (workspace/plan: not per credential)
429QUOTA_EXCEEDEDMonthly quota exhausted

The account-verification and policy codes documented for POST /v1/transactional/send (SENDING_BLOCKED, ACTION_REQUIRED, DAILY_LIMIT_EXCEEDED, MARKETING_PAUSED, and the rest) apply here too.

Partial success is possible: some recipients may be accepted while others are rejected (e.g., suppressed addresses). Check rejectedRecipients in the response.

Next Steps

On this page