Mail by PonyDocs

Guides

Batch sends

View as Markdown

POST /batches sends up to 100 letters from your prepaid balance in one request, with a separate result for each letter. It needs the letters:write and orders:send scopes and an Idempotency-Key header, and works with live keys, OAuth connections and test keys.

Build a batch#

Each item has a client_reference, unique within the batch, and one of:

  • a template_id with variables, plus a recipient (and optionally a sender) or their _id forms. See Templates.
  • a draft_id. That saved draft is sent as it is. If the item also has a recipient, sender or title, a copy of the draft with those changes is sent instead, so one base draft can go to many recipients.
JSON
{"items": [
  {"client_reference": "inv-1001", "template_id": "$template_id", "variables": {"customer": "Sample Recipient", "invoice": "INV-1001", "amount": "$120.00", "due_date": "November 30"}, "recipient": {"name": "Sample Recipient", "line1": "123 Example Avenue", "city": "Exampleville", "state": "CA", "postal_code": "90001", "country": "US"}},
  {"client_reference": "inv-1002", "template_id": "$template_id", "variables": {"customer": "Second Recipient", "invoice": "INV-1002", "amount": "$75.50", "due_date": "December 2"}, "recipient": {"name": "Second Recipient", "line1": "789 Sample Road", "city": "Exampleville", "state": "CA", "postal_code": "90002", "country": "US"}},
  {"client_reference": "inv-1003", "template_id": "$template_id", "variables": {"customer": "Third Recipient"}, "recipient": {"name": "Third Recipient", "line1": "1 Fixture Lane", "city": "Exampleville", "state": "CA", "postal_code": "90003", "country": "US"}}
], "content_policy_acknowledged": true}

Read the result#

Each item is drafted (or rendered), proofed, quoted and sent on its own. One item's failure never stops another.

JSON
{
  "batch_id": "batch_…",
  "status": "completed",
  "environment": "live",
  "summary": {"requested": 3, "queued": 2, "failed": 1, "pending": 0, "debited_minor": 998},
  "items": [
    {"client_reference": "inv-1001", "status": "queued", "order_id": "order_…", "draft_id": "draft_…", "send_intent_id": "send_…", "debited_minor": 499},
    {"client_reference": "inv-1002", "status": "queued", "order_id": "order_…", "draft_id": "draft_…", "send_intent_id": "send_…", "debited_minor": 499},
    {"client_reference": "inv-1003", "status": "failed", "error": {"code": "VALIDATION_ERROR", "message": "Supply every template variable.", "retryable": false, "details": {"missing": ["amount", "due_date", "invoice"]}}}
  ]
}

queued means accepted and paid from your balance, not printed or mailed. Failed items carry the same error object as single requests, such as INSUFFICIENT_FUNDS, VALIDATION_ERROR or ACTION_NEEDED; see Errors.

GET /batches/{batch_id} (scope orders:read) returns the stored result.

Retries and double-send protection#

  • Same key, same body: returns the stored result. If the earlier request was interrupted, it resumes the unfinished items. Nothing is sent twice.
  • Same key, different body: 409 IDEMPOTENCY_CONFLICT.
  • Still running: a retry while the same batch is in progress gets a retryable 503.

client_reference identifies one mailing permanently. To retry failed items, for example after adding credits, send a new batch key with the same items. Items whose client_reference already has an order come back as queued with "already_sent": true and the existing order_id. Earlier failures reuse their draft and are attempted again.

Duplicate client_reference values in one batch are rejected with 422.

Limits#

Batches are processed synchronously. Each letter renders an exact print proof, so allow up to about a second per item and set a generous HTTP client timeout (several minutes for a full batch). The maximum is 100 items. A batch counts as one write against the account's rate limit.

Over MCP, use the create_batch and get_batch tools.