Appearance
Batch sending
A batch sends the same message to many people, each getting their own email. You make one request; MarleyFetch works through the list in the background, pacing itself to your connection's quota.
Batch sending is a Pro feature.
Create a batch
POST /v1/send/batch needs content and recipients:
| Content — pick one | |
|---|---|
template | Inline { subject, text_body?, html_body? }. |
template_id | ID of a saved template. |
| Recipients — pick one | |
|---|---|
recipients | Array of up to 1,000 addresses. |
audience_id | ID of a saved audience. Enables personalisation. |
Optional: name (shown in the dashboard) and from (which connection sends).
When each recipient needs different content, send an emails array instead.
To a list of addresses
bash
curl https://api.marleyfetch.com/v1/send/batch \
-H "Authorization: Bearer $MARLEYFETCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "September newsletter",
"from": "news@yourcompany.com",
"template": {
"subject": "What is new in September",
"html_body": "<p>Here is what we shipped this month.</p>"
},
"recipients": ["alice@example.com", "bob@example.com"]
}'ts
const { batch_id } = await mf.batch()
.name('September newsletter')
.from('news@yourcompany.com')
.subject('What is new in September')
.html_body('<p>Here is what we shipped this month.</p>')
.recipients(['alice@example.com', 'bob@example.com'])
.send()To an audience, personalised
When you send to an audience, {{placeholders}} in the subject and bodies are replaced with each member's payload:
json
{
"name": "Renewal reminder",
"template": {
"subject": "{{first_name}}, your {{plan}} plan renews soon",
"html_body": "<p>Hi {{first_name}}, your {{plan}} plan renews next week.</p>"
},
"audience_id": 12
}A member added with "payload": {"first_name": "Alice", "plan": "Pro"} receives "Alice, your Pro plan renews soon". Placeholders are case-sensitive, and a variable the member does not have renders as an empty string — so a sentence like Hi {{first_name}}, degrades to Hi ,. Keep payloads complete for every variable you use.
Recipients passed as a plain recipients array have no payload, so leave placeholders out of those batches.
Different content per recipient
emails takes up to 1,000 fully formed emails — one recipient, subject and body each — and replaces both the content and the recipients:
json
{
"name": "Order updates",
"emails": [
{ "to": "alice@example.com", "subject": "Order 1042 has shipped", "text_body": "Your order is on its way." },
{ "to": "bob@example.com", "subject": "Order 1043 is delayed", "html_body": "<p>Sorry, your order is a day late.</p>" }
]
}Each item goes to exactly one address; cc and bcc are rejected. For a handful of emails, calling /send for each works just as well.
The response
The request returns as soon as the batch is created:
json
{
"success": true,
"batch_id": 77,
"total_count": 2,
"message": "2 emails queued for batch delivery.",
"status_url": "/v1/batches/77"
}Review before sending
Set "draft": true to create the batch without sending it. The response carries a review_url:
json
{
"success": true,
"batch_id": 78,
"total_count": 3,
"draft": true,
"review_url": "https://app.marleyfetch.com/batches/78",
"message": "Draft of 3 emails created. Nothing is sent until its owner presses Start at review_url.",
"status_url": "/v1/batches/78"
}On that page you step through each recipient's copy with their variables filled in, and see how many recipients are missing a variable. Start sending begins delivery; Discard deletes the draft. Only the dashboard can start a draft, so an agent can prepare a batch over MCP with draft_batch and you stay the one who sends it.
The recipient count is taken when you press Start, so members added to the audience in between are included. Until then, GET /v1/batches/{id} reports "draft": true and status: "pending".
Track progress
Poll GET /v1/batches/{id}:
bash
curl https://api.marleyfetch.com/v1/batches/77 \
-H "Authorization: Bearer $MARLEYFETCH_API_KEY"json
{
"success": true,
"batch": {
"id": 77,
"name": "September newsletter",
"status": "running",
"total_count": 500,
"sent_count": 320,
"failed_count": 2,
"progress": 64,
"created_at": "2026-09-28 10:15:00",
"updated_at": "2026-09-28 10:21:40"
}
}status moves from pending to running to completed (or failed). progress is the percentage of recipients handled, sent or failed. Once a minute is plenty for polling; the Batches page shows the same numbers, plus each failed recipient with its error and a retry button.
How pacing works
A batch never breaks your connection's limits:
- It sends only as fast as the connection allows.
- When the connection's quota is used up, the remaining emails wait for the next window and continue — a 5,000-recipient batch on a 2,000/day connection takes three days, and nothing fails.
- If your provider starts rate-limiting, the batch backs off and resumes.
Only permanent problems, such as a deleted connection or an address your server refuses, count as failures.
Delete a finished batch
bash
curl -X DELETE https://api.marleyfetch.com/v1/batches/77 \
-H "Authorization: Bearer $MARLEYFETCH_API_KEY"Only completed or failed batches can be deleted; others return 400 INVALID_STATUS.
Errors specific to batches
| Status | Body | Cause |
|---|---|---|
| 400 | BATCH_TOO_LARGE | More than 1,000 recipients or emails. Use an audience instead. |
| 400 | {"error": "Invalid audience"} | audience_id does not exist or is not yours. |
| 400 | {"error": "Audience has no members"} | The audience is empty. |
| 404 | RESOURCE_NOT_FOUND | template_id does not exist or is not yours. |
| 403 | FEATURE_NOT_AVAILABLE | Batches need the Pro plan. |