Blog
AI AgentsJuly 28, 202612 min read

AI Outreach Agent Guardrails: 7 Real Controls

Stop an autonomous outreach agent from overspending, over-scraping, or sending unapproved email. Seven verifiable API guardrails, explained honestly.

Seven named guardrails - spend caps, robots.txt handling, a human approval gate - wrapped around an autonomous outreach agent's pipeline.

An autonomous outreach agent that finds businesses and drafts email is only as trustworthy as the guardrails around it. This guide walks through the concrete, verifiable controls biz collect and AutoEmail expose - spend caps, robots.txt handling, render limits, a mode gate that structurally blocks autonomous sending, batch ceilings, rate limits, and MX-level email verification - and is equally honest about what each one does not do.

Why an Agent Needs Guardrails, Not Just a Prompt

Telling an agent "don't overspend" or "be respectful when scraping" in a system prompt is not a guardrail - it is a suggestion the model can misread, forget after a long context window, or simply override when a user pushes back. A guardrail is a control the API enforces regardless of what the model decides: a hard number, a status code, a gate the agent cannot argue its way past.

The AI agent lead generation pipeline guide covers how biz collect (find) and AutoEmail (draft) chain into one loop. This guide is the other half: the specific, named controls that keep that loop from spending unbounded credits, scraping sites that asked not to be scraped, or sending something nobody approved. Every control below is a real, documented parameter or status code from one of the two products' APIs, not a best practice you have to take on faith.

Guardrail 1: max_credits Caps Search Spend

Failure mode: An agent runs a coverage: "exhaustive" search over a wide radius. Exhaustive coverage subdivides the search area into a grid of tiles and queries each one to escape Google's 60-result-per-query ceiling, and cost scales with tile count: credits_charged = tile_count x result_pages x unique keyword queries x 20. Tile count grows with radius, up to a 64-tile hard cap per keyword - so a wide, unbounded exhaustive search can spend far more than a standard one before anyone notices.

The control: Set max_credits on the search request. If the full tile grid's cost would exceed it, biz collect truncates the tile list, keeping the most central tiles, so the charge fits under the cap, and flags the job coverage_truncated: true. The minimum is 20 credits, the cost of a single search tile; a cap below what one tile costs is rejected with a 400 before anything runs.

curl -X POST "https://bizcollect.dev/api/v1/search" \
  -H "Authorization: Bearer biz_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "location": "Munich, DE",
    "keywords": ["physiotherapy clinic"],
    "radius_km": 25,
    "coverage": "exhaustive",
    "max_credits": 400,
    "wait": true
  }'

What it does not do: max_credits only bounds spend on this search. It does not cap how many searches the agent runs in a loop, and it does not make a truncated, coverage_truncated: true result complete - the agent (or you) has to decide whether partial coverage is good enough for the task, or whether to raise the cap and re-run.

Guardrail 2: Respectful Scraping and robots_skipped_count

Failure mode: An email-scraping agent hits every business website in a result set regardless of whether the site's own rules say it may, which is both a compliance risk and a fast way to get an IP range blocked.

The control: scrape_mode: "respectful" is the default. It fetches each site's robots.txt and honors it per RFC 9309 for the BusinessDataCollector agent: if a site disallows its homepage, the whole site is skipped; if it disallows specific paths, only those paths are skipped. A missing, unreachable, or unparseable robots.txt is treated as allow-all. The job reports exactly how many sites were skipped this way in robots_skipped_count, so the agent (or a human reviewing the job) can see the control actually fired rather than trusting it silently.

{ "scrape_emails": true, "scrape_mode": "respectful", "resolve_contacts": true }

scrape_mode: "aggressive" exists and ignores robots.txt entirely - it is documented as being for cases where you are responsible for, and entitled to, scrape the target sites. It is not the default, and this guide is not recommending it as one.

What it does not do: Honoring robots.txt is a crawling courtesy, not a permission slip. A site with a permissive robots.txt has not thereby consented to receive your outreach email, and a site with none at all (treated as allow-all) has said nothing about its preferences either way. Respectful mode governs scraping, not the legality or etiquette of what you do with the data afterward.

Guardrail 3: render_fallback: false Caps Render Spend

Failure mode: render_fallback (default on) re-fetches a site through a headless renderer when the plain-HTML scrape finds nothing and the page looks JS-built - useful for recovering real contact data from a Wix or React site, but each rendered site costs +1 credit, charged at job completion. An agent running many jobs across JS-heavy sites can rack up render charges it did not explicitly plan for.

The control: Set render_fallback: false for a hard guarantee of zero render charges. The trade-off is explicit and worth stating plainly: you will miss contact data on sites that only render their content client-side. The job always reports what happened either way, via rendered_sites_count and render_credits_charged.

{ "scrape_emails": true, "render_fallback": false }

What it does not do: Disabling render fallback does not disable scraping itself, and it does not change how scrape_mode treats robots.txt. It only removes one specific, metered cost.

Guardrail 4: human_in_the_loop Structurally Blocks Sending

Failure mode: This is the guardrail the whole cluster keeps returning to, because it is the one that matters most: an agent that can find businesses and draft copy should not be able to also decide, on its own, that a given draft is good enough to send.

The control: AutoEmail keys carry a mode, fixed at key creation, and it is enforced at the API level, not by convention. A human_in_the_loop key's writes always land as 201 {"status": "pending_approval", "emailId": "..."} in the dashboard's approval queue, regardless of what the request asked for. POST /emails/{id}/approve - the endpoint that actually sends - returns 403 mode_not_allowed for these keys. The agent is not merely discouraged from sending; the operation that would send is not reachable with that key at all. Only a full_autonomous key can call /approve, and each successful call consumes one quota unit.

curl -s "https://courteous-gopher-315.eu-west-1.convex.site/api/v1/emails/abc123/approve" \
  -H "Authorization: Bearer ak_live_<human_in_the_loop key>" \
  -X POST
# 403 mode_not_allowed

For cold outreach, this is the correct default, not a limitation to route around: a spend cap or a robots check can be verified by a machine, but whether a specific message to a specific business is a good idea to send right now is a judgment call, and judgment calls belong to a person.

What it does not do: Human-in-the-loop mode guarantees a human sees the draft before it sends. It does not guarantee the human reads it carefully, and it does not vet the content for legal compliance on your behalf - the approval queue is a checkpoint, not a compliance review.

Guardrail 5: The 100-Recipient Batch Ceiling

Failure mode: A single runaway API call could, in principle, blast a campaign to an unbounded list in one shot.

The control: POST /outreach/batch caps every call at 1 to 100 recipients, in both mode: "final" (your fixed subject/body, {{name}}/{{email}} substituted per recipient) and mode: "generate" (AutoEmail drafts each email from a brief plus per-recipient context). A larger list has to be split into multiple calls, each independently subject to the mode gate above and to quota. Pair it with Idempotency-Key and dedupeWindowHours so a retried or re-run batch does not double-contact anyone.

curl -X POST "https://courteous-gopher-315.eu-west-1.convex.site/api/v1/outreach/batch" \
  -H "Authorization: Bearer ak_live_..." \
  -H "Idempotency-Key: munich-physio-batch-1" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "generate",
    "businessId": "<autoemail account id>",
    "send": false,
    "brief": "Introduce our booking widget, reference their reviews naturally.",
    "recipients": [ { "email": "...", "name": "...", "context": "..." } ]
  }'

What it does not do: A 100-recipient ceiling limits blast radius per call, not campaign size overall. An agent that loops the call ten times in a row has still reached a thousand recipients - the ceiling is a per-call brake, not a program-level one, so pair it with your own tracking of how many batches an agent has run.

Guardrail 6: Rate Limits Throttle Every Route

Failure mode: A misbehaving loop (a retry storm, a bug that resubmits the same request) can hammer an endpoint far faster than any legitimate agent workflow would.

The control: Every route on both APIs is rate-limited per key. On AutoEmail: GET /me allows 60 requests per key per 60 seconds, POST /outreach/batch and POST /emails/{id}/approve are throttled per minute, and a 429 response is the API pushing back rather than silently dropping or queuing requests. biz collect enforces its own per-route limits and returns the shared RateLimited response when they are hit. Treat a 429 as a signal to back off and check GET /usage (AutoEmail) or the job record (biz collect), not as a transient error to blindly retry into.

What it does not do: A rate limit protects the API and your own key from runaway loops. It does not review the content of what is being sent within that rate - a well-behaved agent sending 30 approvals a minute is still bound by every other guardrail on this page.

Guardrail 7: MX Verification Is Not a Mailbox Guarantee

Failure mode: Treating an email address's confidence: "high" tag as proof a human will actually receive the message, and skipping any other check before it goes into an outreach batch.

The control: biz collect verifies every extracted address with syntax validation plus an MX lookup over DNS-over-HTTPS (with an A/AAAA implicit-MX fallback), tagging each one high, medium, low, or unknown. That is real signal: it filters out malformed addresses and domains with no mail infrastructure at all, for free, on every search.

{
  "email": "marco@trattoriadamarco.example",
  "confidence": "high",
  "has_mx": true,
  "is_role_account": false
}

What it does not do, plainly: There is deliberately no SMTP RCPT TO mailbox probe - it is not reliable to run from a serverless environment, and attempting it at scale damages sender reputation for everyone on the platform. A high-confidence address means the domain accepts mail and the address looks like a real, monitored inbox; it is not a guarantee that specific mailbox exists or that a message to it will be delivered. Build bounce handling into whatever sends the mail regardless of confidence tier.

What a Full Guardrail Stack Looks Like Together

None of the seven controls above substitutes for another. Stacked, a conservative autonomous-feeling run looks like this:

  1. max_credits bounds what the FIND step can spend, and coverage_truncated tells you if it hit that ceiling.
  2. scrape_mode: "respectful" (the default) and, optionally, render_fallback: false bound what gets crawled and rendered, with robots_skipped_count as the receipt.
  3. resolve_contacts (also default) turns emails into named contacts and role-account flags, so the DRAFT step can greet a real person or fall back to the business name honestly instead of guessing.
  4. POST /outreach/batch stages drafts up to 100 recipients at a time, in generate or final mode, behind an Idempotency-Key and a dedupe window.
  5. A human_in_the_loop key means every one of those drafts lands as 201 pending_approval and stays there - /emails/{id}/approve simply is not callable with that key.
  6. Rate limits throttle every step of the above regardless of how the agent's loop is written.
  7. MX-level confidence filters obviously bad addresses before they ever reach a human's approval queue, without pretending to guarantee delivery.

Full details on stages FIND and DRAFT, including the field-by-field personalization payload and the cost model behind it, live in the AI agent lead generation pipeline guide. If you are still deciding whether an API-driven pipeline is the right fit at all versus a manual process, the AI lead generation agent guide is the place to start, and the mcp-server-for-business-data guide covers wiring the FIND side into an MCP tool an agent can call directly. The API docs are the source of truth for every parameter named above.

The Bottom Line

Guardrails for an autonomous outreach agent are not a philosophy, they are seven specific, testable controls: a spend cap on search, a robots.txt-respecting default with a visible skip count, an opt-out for render charges, a mode gate that makes the send endpoint unreachable rather than merely discouraged, a 100-recipient batch ceiling, per-route rate limits, and MX-level (not mailbox-level) email verification. Every one of them is honest about its edges - a cap is not compliance, a robots check is not consent, and a high confidence score is not a guaranteed inbox. Put together, they are what lets FIND and DRAFT run with real autonomy while the one step that actually matters, the send decision, stays with a human.

Frequently asked questions

What stops an outreach agent from overspending on biz collect?
max_credits caps the spend on an exhaustive search: if the full tile grid would cost more, biz collect truncates the tile list to fit the cap and flags the job coverage_truncated: true. The minimum is 20 credits, the cost of one search tile. It caps a single search's spend, not how many searches an agent runs in a loop.
Can an AI agent send emails on its own with AutoEmail?
Not with a human_in_the_loop key, which is the default. Every write lands as a pending draft (201 pending_approval) and POST /emails/{id}/approve, the endpoint that actually sends, returns 403 mode_not_allowed for that key. Only a full_autonomous key can call /approve, and that mode is opt-in for cases where unattended sending has been deliberately accepted.
Does respectful scraping mode make scraping legally safe?
No. scrape_mode: respectful honors a site's robots.txt per RFC 9309 and reports what it skipped in robots_skipped_count, which is a crawling courtesy. It says nothing about whether the site or its listed contacts have consented to receive outreach afterward - that is a separate judgment call.
How many recipients can one outreach batch reach?
POST /outreach/batch accepts 1 to 100 recipients per call, in either mode: final (template substitution) or mode: generate (AI-drafted per recipient). A larger list requires multiple calls, each still gated by the key's mode and by quota, so the ceiling limits blast radius per call rather than total campaign size.
Does a high email confidence score mean the mailbox definitely exists?
No. biz collect verifies syntax plus an MX lookup over DNS-over-HTTPS; it deliberately does not perform an SMTP RCPT TO mailbox probe, since that is unreliable from a serverless environment and harmful to sender reputation at scale. A high confidence tag means the domain accepts mail and the address looks like a real inbox, not a guarantee of delivery.
What happens if AutoEmail's rate limit or quota is hit mid-run?
Rate-limited requests return 429 and should trigger backoff, not a blind retry loop. Quota exhaustion returns 402 quota_exceeded on the next billable write. GET /usage reports the remaining quota so an agent can self-throttle before hitting either limit rather than after.
Are these guardrails enough to make autonomous outreach compliant?
No, and none of them claim to be. A spend cap, a robots.txt check, and MX verification are technical controls against overspending, over-scraping, and obviously bad addresses. They do not determine whether a specific cold email is lawful to send to a specific recipient in a specific jurisdiction - that judgment is exactly why the human-in-the-loop approval gate is the default.

Turn the article into an API call.

Use biz collect free: 200 signup credits, no credit card, and a clean JSON contract your agent or workflow can call today.

No credit card required200 signup credits20 daily login credits