A batch is a drafting job with a review gate at the end of it. Everything on this page is about that job: where it is, why it is not moving, and what it has spent. GET /v1/email/batches/{batch_id} is the whole picture in one call.

Batch statuses

The dashboard renames two of these, and the renames matter enough to learn both.
drafted means the drafts are ready, not that the batch is finished. It says nothing about how many emails have been sent — that is what counts is for. A batch can sit at drafted forever with zero rows sent, and that is a normal end state, not a stall.
The status filter on GET /v1/email/batches takes these same six strings.

next_draft_at

The clock, not a bug. It carries the instant the next drafting pass may claim a row: a start_at in the future, or the next opening of the batch’s calling_window, in the batch’s own timezone. It is null in three cases, all of which the status already tells you:
  • the batch is drafting right now,
  • a person paused it,
  • it is over — drafted, canceled or failed.
So a drafting batch with a next_draft_at some hours out is waiting for its hours to open, and a drafting batch with next_draft_at: null and nothing moving in counts is something to look at.

When a batch stops itself

Three things stop drafting, and they are deliberately different shapes. Four more things stop a batch outright, each with a sentence in failure_reason:
  • the person who created it is no longer in the workspace,
  • that person is no longer an admin or editor — every pass re-authorizes as the creator, exactly as an HTTP request from them would,
  • the task that drafts it has been deleted,
  • an edit to the task broke the field map. It is re-checked on every pass, not only at create, because a task has no versions: rename the output field the mapping points at and the next pass has nothing to map.
A missing_vars failure never counts toward the ten. A blank cell in a column the task requires is a fact about that row’s data, not about the batch — ten of them in a row would otherwise stop a batch whose other 9,990 rows are fine. It also costs nothing: the check runs before the model is reached.
When the breaker trips, the batch goes to failed, failure_reason carries the last thing that went wrong, and email_batch.failed fires. Rows already drafted are untouched and can still be sent.

Why one row failed

last_error on a recipient is the row-level answer. During drafting it reads {error_type}: {message}:
A row whose attempts are spent lands in draft_failed and waits for a person. POST .../retry puts it back to pending with its attempt count reset — see reviewing and sending. One more row-level error has no type prefix: the worker drafting this row stopped before it finished. That is a process that died mid-run, and the row is retried like any other transient failure.

Send failures

A send failure is classified by who owns the problem, and the class decides what happens next. For a send failure, last_error is the provider’s own message rather than a typed prefix. Anything not recognised is treated as transient on purpose: a send that might have landed must not be written off as permanently failed, and the retry costs nothing because the idempotency key makes a re-send of a successful email a no-op.

Pause, resume, cancel

Anything else is a 409 naming the current status.
A batch cannot be deleted. Cancel is the only terminal action, because a batch is the record of what was sent to whom — the addresses, the wording, the sending domain and the time. Deleting one would delete the answer to “what did we send this person”.

Cost

provider_cost on the batch is the running total of what drafting has spent: every row’s task run, summed. It is null until the first run has a cost. That is your own model spend on your own provider keys. Talqing charges no platform fee on an email batch, and it does not meter whatever the task’s tools and MCP servers charge per call — a lookup that spends a vendor’s credits per row spends them 4,200 times on a 4,200-row batch, and shows up on that vendor’s bill, not here.
Task spend does not appear in the Observability charts. Those are built on calls and conversations; a task run is neither. The number on the batch is the only place this spend is reported, and there is no cap in front of it. Every row runs the task and is billed whether or not it is ever sent.

Webhooks

Three events, all about drafting, all carrying the same batch body a GET would return.
There is deliberately no event for a completed send. A send is fifty rows in a few seconds, and one event per press would be noise. To watch sending, read the row statuses; to watch delivery, watch Resend, which is where bounces and complaints live.
See webhooks for the envelope, the signature scheme and delivery records.

Next

Reviewing and sending

Editing rows, the 50-row cap, duplicates, skip, restore and retry.

Agent task errors

What each error.type means on the run that produced it.