This guide builds a two-part pipeline: an agent task that researches a company from its domain and returns a typed result including a subject line and body, and an email batch that runs the task once per row of your list, shows every draft for review, and sends only when you create a send.
Only send to people who opted in. Mail goes through your own Resend account, and Resend’s acceptable-use policy forbids cold outreach, purchased lists and scraped contacts. See email overview.Drafting has no spend cap. Every row runs the task on your provider keys, plus whatever its integrations charge per call, whether or not the draft is ever sent.
You need: an LLM provider key, a Tavily API key, a Resend account with a verified sending domain (email setup), and the EDITOR role.

1. Connect Tavily and narrow its tools

GET /v1/integrations/{integration_id}/mcp-tools lists the tools live from the provider, which also proves the key works. A new integration approves every tool (allowed_tools: null); narrow it to the two this task needs, so the model chooses from fewer:
The list is replaced whole and cannot be empty. See MCP servers.

2. Write the task

task.json
What matters in this config:
  • vars are the inputs. The batch fills them from CSV columns of the same name. A required variable with no column and no default is refused when the batch is created.
  • Each output description is the prompt for that value. It becomes the argument description of the generated submit_result tool. Say what good looks like and when to send null.
  • required: false lets a field be null. Every field must still be sent, so “could not find it” is explicit rather than a missing key. See inputs and output.
  • max_steps (default 25, max 50) counts model → tools → model rounds, not tool calls. timeout_seconds (default 300, max 600) is wall-clock and usually the real bound. See errors and limits.

3. Run it once and read the result

task_version: "draft" runs the unpublished config. The request blocks until the run finishes, so set your client’s timeout above timeout_seconds (the Python SDK defaults to 30 seconds).
Check, in order: Run it on a domain you know well, so you can tell a true sentence from a plausible one. When it is right, publish — a batch uses the published version:

4. Connect Resend

The senders call returns the account’s verified domains, read live. Any address on a verified domain can be from_email; one on an unverified domain is refused.

5. Create the batch

Each row has one flat column space: your CSV’s columns plus the task’s output fields. field_map points to, subject and body at names in that space — a CSV header or a string output field. Here the address comes from the CSV and the words from the task.
  • Drafting starts immediately and runs at any hour; window applies to sending. draft_concurrency (1–10, default 5) is how many rows draft at once.
  • To have the task find the address too, add an output field such as work_email and map "to": "work_email".
  • Refused at create, in one 400: a mapped name that is neither a column nor an output field, a mapped output that is not a string, a CSV header that collides with an output name, a required task variable with no column or default, an unverified from_email. See creating a batch.
  • A column that exists but is blank on some rows passes create; those rows fail at drafting with invalid_input, without running the model.
In the dashboard this is Email → Outbound → New batch.

6. Review and send

When drafting finishes, the batch status is drafted (Ready to review). Nothing has been sent. List the drafts; not_ready_reason says why a row cannot go yet:
Fix a cell with an override. Overrides are stored separately from what the task wrote and always win, including over a later redraft:
Then create a send. scope: "all" takes every draft; scope: "selected" with recipient_ids takes only those rows.
send_daily_cap (default 200 a day per batch) is what protects a new sending domain’s reputation. Raise it gradually, or use a ramp — see creating a batch. At 45 seconds apart and 100 a day, 1,000 rows take ten sending days.
A send can be paused, resumed or cancelled; cancelling returns every unsent row to draft. See reviewing and sending.

Test it

  1. Run the task by hand on a domain you know. If specific_detail is generic, every row will be.
  2. Create a two-row batch with your own addresses and watch scheduled → drafting → drafted.
  3. Blank one row’s domain before upload; it should fail alone with invalid_input.
  4. Send both to yourself and check line breaks, from_name and reply-to.

What to know before scaling

  • Republishing the task mid-batch changes later rows. drafted_versions on the batch shows the split; POST /v1/email/batches/{batch_id}/redraft with selection: "stale_version" reruns the older rows, at the cost of another task run each.
  • Dedup is per batch. One address is mailed at most once per batch; the same person on two batches gets two emails. Suppression of bounces and complaints is Resend’s.
  • Sent means Resend accepted it. Delivery, bounces and complaints are in the Resend dashboard.
  • A batch stops itself after ten consecutive drafting failures or one configuration failure. See monitoring.

Next

Reviewing and sending

Skipping rows, redrafting and the send in full.

Task tools and MCPs

What a task can reach while it works.