GET /v4/jobs/{jobId} for progress and GET /v4/jobs/{jobId}/items for per-recipient outcomes. Mirrors POST /contacts/bulk/async exactly./messages/{channel}/audience.type and basis sit on the envelope, as in the synchronous batch, and for the same reasons.expiresAt). Send Idempotency-Key to make retries safe: a replay returns the ORIGINAL job rather than enqueuing a second one.curl --location '/messages/email/batch/async' \
--header 'Authorization: Bearer <token>' \
--header 'Idempotency-Key: 0f3e2b1a-7c4d-4e2a-9b11-2f6d8c1a5e30' \
--header 'Content-Type: application/json' \
--data '{
"type": "transactional",
"basis": "contract_performance",
"messages": [
{
"to": {
"contactId": "string",
"clientContactId": "string",
"email": "string",
"name": "string"
},
"cc": [
{
"email": "string",
"name": "string"
}
],
"bcc": [
{
"email": "string",
"name": "string"
}
],
"from": {
"email": "string",
"name": "string"
},
"replyTo": {
"email": "string",
"name": "string"
},
"templateId": 0,
"subject": "string",
"htmlContent": "string",
"textContent": "string",
"language": "string",
"variables": {},
"attachments": [
{
"name": "string",
"content": "string",
"url": "string"
}
],
"headers": {},
"tracking": {
"opens": true,
"clicks": true
},
"tags": [
"string"
],
"metadata": {},
"sendAt": "string",
"createContact": true,
"retainBody": true,
"event": "string",
"permId": "string"
}
]
}'{
"jobId": "string",
"type": "string",
"status": "string",
"progress": {
"total": 0,
"processed": 0,
"succeeded": 0,
"failed": 0
},
"createdAt": "string",
"startedAt": "string",
"finishedAt": "string",
"expiresAt": "string",
"error": "string",
"links": {
"self": "string",
"items": "string"
}
}