POST /v1/email/batches uploads a list, the agent task that drafts each row, a connected Resend integration and a field_map. The batch is created scheduled and starts drafting. Nothing is emailed until you POST /v1/email/batches/{batch_id}/sends. Before you start, read Email outbound for the acceptable-use rule and Resend setup.

Create a batch

recipients[].input is one CSV row keyed by header. Values are strings. Leave out empty cells instead of sending "". In the dashboard, Email → Outbound → New batch parses a CSV in the browser and posts the same request. The dashboard’s confirm dialog states how many task runs you are about to pay for.

The column space and field_map

Each row has one flat set of columns, merged in this order. A later layer wins: The merged result is the row’s columns. field_map.to, .subject and .body can each name either a CSV header or a task output field. For example, a list that already has addresses maps to to a CSV column such as email. A list of phone numbers maps to to an output field such as work_email, which the task looks up with its tools. Output fields you do not map are kept on the row and shown in review, but they are never sent. Requirements on the task:
  • Mapped output fields must be string. A boolean, integer or number output field cannot be mapped.
  • Each row’s uploaded columns, plus edits to those columns, become the task’s {{vars.*}}. All of them are passed, not only the ones the task declares. See inputs and output.
  • The task must be published. Drafting always uses the current published version. Publishing or rolling back a version that would break a live batch’s field map is refused. Deleting the task is refused while a batch using it is scheduled, drafting or paused.
A row is sendable when all three mapped names resolve to non-empty strings, to parses as an email address, and subject has no line break. When a mapped column that the task does not write is blank or invalid, no draft could fix it. That row is skipped with skip_reason: "unfillable" at upload, is never drafted, and costs nothing. skips.unfillable on the response counts these rows.

What is refused at create

Every problem is returned at once in one 400:
  • A mapped name that is neither a CSV header nor an output field of the published task, or that maps to a non-string output field.
  • A CSV header that is also a task output field name. Rename one of them.
  • A required task variable with no matching column and no default.
  • A header that is not a valid variable name. Headers must use letters, digits and underscores and start with a letter or underscore.
  • A from_email whose domain is not verified on the account, or a from_email or reply_to that is not an address.
  • An integration that is missing, is not Resend, or is not active. A task that is missing or has never been published.
  • start_at in the past or more than 90 days ahead. Zero rows, or more than 10,000 rows in one request. Add more rows with add recipients.
The required-variable check looks at headers, not cells. A row whose required column is blank fails at drafting with invalid_input. Only that row fails, and no tokens are spent. Fill the cell and redraft it.

Batch fields

The four send_* fields are defaults. Each send copies them when it is created. PATCH /v1/email/batches/{batch_id} changes them for the next send. Use PATCH .../sends/{send_id} to change a send that already exists. A running batch or send applies a policy change within five minutes.

Warming up a new domain

send_daily_cap is either {"kind": "fixed", "limit": 200} (limit ≥ 1) or a ramp:
A ramp starts at start emails a day and rises by step every interval_days (1–30) until it reaches end (end > start). It counts only days the batch actually sent, so weekends, closed windows and pauses do not advance it. send_daily_cap_today shows the limit in force today. Editing a ramp keeps its progress unless you change start. Each batch ramps separately, so two batches on the same domain together send the sum of their caps.
send_daily_cap is your main protection for a domain’s reputation. A new domain that sends thousands of emails in an afternoon can lose inbox placement, and no review step reverses that. Raise the cap gradually.

Review the drafts

GET /v1/email/batches/{batch_id}/recipients?status=draft&limit=50 returns rows in upload order (limit up to 200, with offset). A draft is always sendable. A row whose task run succeeded but produced nothing sendable, for example because no address was found, is draft_failed. The reason is in last_error, and the task output is kept so you can read what the task did find. Export CSV on the batch page in the dashboard downloads every row. It contains your columns, the output fields, status and error. Values are written raw, so a cell starting with = opens as a formula in a spreadsheet.

Edit a cell

PATCH /v1/email/batches/{batch_id}/recipients/{recipient_id} writes overrides:
  • An edit never overwrites output, and a redraft never overwrites an edit.
  • An empty string clears the edit and falls back to the layer below.
  • Editing one of your uploaded columns changes what the task receives the next time the row drafts.
  • The row’s status is recomputed. Filling a missing mapped cell moves draft_failed back to draft, and moves an unfillable row back to pending so it gets drafted. Clearing a mapped cell moves a row the other way. The response includes the new status.
  • queued rows can still be edited. Once a row is sending, sent or send_failed, an edit returns 409.

Delete rows

POST /v1/email/batches/{batch_id}/recipients/delete with {"recipient_ids": [...]} permanently removes rows that were never sent: pending, draft (including queued), draft_failed or skipped. Other rows are returned in rejected with a reason.

Redraft

POST /v1/email/batches/{batch_id}/redraft with either recipient_ids or a selection resets rows to pending. It clears output, task_run_id, attempts and last_error, and the rows draft again.
Every redrafted row runs the task again and is billed again, and its previous draft is discarded. Check the count before you redraft.
Edited cells survive a redraft unless you pass clear_overrides: true. unfillable rows are never redrafted. Redrafting moves a drafted or failed batch back to drafting. A paused batch stays paused. A canceled batch returns 409. A typical way to improve drafts mid-batch: pause the send, publish a better task, redraft stale_version, then resume the send. The send picks up each new draft as it lands.

Send

A send emails real people. A sent email cannot be recalled. You can pause or cancel a send before it finishes.
Several selected sends can be live at once, but a row belongs to only one. An all send cannot be live alongside any other send of the same batch. That conflict returns 409. Send fields. Any field you omit is taken from the batch:
  • start_at: at most 90 days ahead.
  • timezone, window: an explicit null window means any hour.
  • send_gap_seconds, send_attempts, send_retry_after_minutes, send_daily_cap: an explicit null cap means uncapped.
  • from_email, from_name, reply_to: apply to this send only. An overridden from_email is checked against your verified domains.
PATCH /v1/email/batches/{batch_id}/sends/{send_id} changes the pacing, window and cap, and start_at before the send begins. It never changes the sender or scope. To change which rows a send covers, cancel it and create another. A send’s counts has total (null for all), queued, drafting, sending, sent, send_failed and skipped. next_send_at and next_send_reason show what the send is waiting for. See monitoring.
Work out how long a send will take before you start it. For example, 1,240 rows at 45 seconds apart is about 15 hours of sending. With a 09:00–17:00 window and a cap of 150 a day, that is nine working days.

Duplicates and resends

  • An address is emailed at most once per batch. The check runs when a send claims the row, because the task may be the one that found the address. A second row with the same address is skipped with skip_reason: "duplicate_recipient".
  • There is no deduplication across batches, and Talqing keeps no suppression list. Resend keeps one on your account.
  • Each email carries an idempotency key, the row’s id, which Resend honours for 24 hours. A row interrupted by a crash is resent under the same key and is not delivered twice.

Adding rows later

POST /v1/email/batches/{batch_id}/recipients appends up to 10,000 rows to any batch that is not canceled or failed. A drafted batch starts drafting again. Duplicates are dropped before drafting:
  • If to maps to an uploaded column, a row whose address is already in the batch is dropped.
  • Otherwise, a row whose uploaded cells exactly repeat another row’s is dropped.
The response has added, skipped (each entry has row_number and reason, plus recipient_id if the row was stored as unfillable), total_recipients and standing_send_id.
If the batch’s most recent send was an all send that has finished, adding rows reopens it. The new rows are then emailed without review. standing_send_id names that send. Otherwise new rows wait for you to review them.

Row statuses

Fields locked after the first send

Once the batch has a send, or any row is sending, sent or send_failed, a PATCH to from_email, from_name, reply_to, body_format or field_map returns 400. name, timezone, window, the drafting and sending fields, and send_daily_cap stay editable. A batch can be edited only while it is scheduled, drafting, paused or drafted.

Next

Monitoring

Statuses, counts, why a batch or send stops itself, webhooks and cost.

Research and outreach

A task that researches each lead, feeding an email batch end to end.