Sending Safely with AI

An AI can only send through Flomailr's own send path, and that path enforces its rules on the server. None of them are settings the AI passes in. The AI can ask for a send. It cannot talk its way past the checks.

There are two send tools:

  • send_email: a personal or one-to-one message to 1 to 5 addresses. Not marketing.
  • send_campaign: an existing campaign, sent to its list.

Both start as a dry run.

Dry run, then confirm

Both tools default to dryRun: true. A dry run sends nothing and reports what would happen:

  • send_campaign returns the audience counts (total, allowed, blocked, deferred), the blocked addresses with the reason for each, the deferred addresses with the time they can go, and how much of today's daily cap is left. Counts are exact. The address lists show up to 25 of each, so a large list does not flood the chat.
  • send_email returns the exact From address, the subject, the size of the rendered HTML, who would receive it and who is blocked.

To send for real the AI must call again with dryRun: false and confirm: true. With dryRun: false and no confirm, nothing is sent and the result is BLOCKED. Show yourself the dry run before you agree to the second call.

You can force dry runs. In Settings > Guardrails, turn on Agents must opt in to real sends. While it is on, the send tools report a dry run even when the AI asks for a real send.

What the server checks

CheckWhat it does
Suppression listAddresses that hard bounced, complained, unsubscribed or were blocked by hand are skipped. send_email always skips them. For send_campaign the check is on by default and the send also drops suppressed addresses on its own. Blocked reason: SUPPRESSED
Blocked domainsAny recipient on a domain in your never-send list is skipped. Reason: BLOCKED_DOMAIN
Daily send capAn optional ceiling on messages per calendar day, counted in your default time zone. Recipients past the cap are blocked. Reason: DAILY_CAP. Both send tools respect it
Quiet hoursAn optional window, such as 21:00 to 08:00, where send_campaign holds messages back. It uses each contact's own time zone and falls back to your default. Those recipients are deferred, not dropped. send_email does not use quiet hours
Master switchIf Enforce sending guardrails is off, every guarded send is blocked rather than sent unchecked
SuspensionA suspended workspace cannot send at all
Monthly plan limitA send that would pass your plan's monthly limit is refused
Invalid addressesMalformed addresses are blocked (INVALID_EMAIL)

send_campaign also needs the campaign to have a list with subscribed contacts, and your workspace needs a business mailing address, because campaign emails carry it in the footer. It sends list-based campaigns only. A campaign aimed at a segment must be sent from the dashboard.

The AI tools sort recipients one by one, so a campaign can go out to some people while others are blocked or deferred. Sends started from the dashboard, the REST API or a schedule are stricter: a send that would pass the daily cap, or that starts inside quiet hours, is refused as a whole, and a scheduled one is retried after the window ends.

Admins set the cap, quiet hours, blocked domains and switches under Settings > Guardrails. Members can read them. See Sending Health.

The 5-recipient cap

send_email accepts at most 5 recipients. This is a hard limit in the server. A request with 6 or more is refused, and so is a request with any invalid address, so nothing is silently dropped. Duplicates are removed.

send_email adds no tracking pixel and no marketing unsubscribe footer, because it is meant for a personal note. The message can be a designed email (the AI passes the design and Flomailr renders it), your own HTML, or plain text. If the rendered HTML is larger than 102 KB, the result warns that Gmail will clip it.

For anything bigger than five people, the AI should save a campaign and use send_campaign.

Idempotency

Both tools take an idempotencyKey. The AI should pass a stable one on any real send.

  • Calling again with the same key and the same request returns the original result, marked REPLAYED, and sends nothing.
  • The same key with a different request is rejected.
  • The same key while the first call is still running returns BLOCKED.

Campaign sends have a second layer. Recipients go out in batches of 100, and each accepted batch is recorded before the next one starts. If a large send is cut short, it continues in the background and finishes by itself. The result then says mode SENDING. The AI should not call send again, and if it does, the repeat only resumes the same send and never mails anyone twice.

Quiet hours override

send_campaign has an overrideQuietHours option, which lets a real send ignore your quiet-hours window. It affects quiet hours only. It does not change suppression, blocked domains, the daily cap, suspension or the monthly limit. If you do not want a send to go out at night, ask your AI not to use it.

Everything is logged

Each action goes into an audit log: dry runs, sends, direct sends, saved drafts, suppression changes, policy reads, readiness and compliance checks, performance reads and brand updates. Each entry records the tool, a plain summary, the campaign and the idempotency key.

  • Owners and admins read it under Settings > Activity, filtered by action.
  • Your AI can read it with get_action_log.

The log is append-only. Revoking a key or disconnecting an app does not erase what it did.

Who can change what

When the connector is signed in over OAuth, it acts as you. Adding or removing suppression entries, and saving the brand kit, need an owner or admin role. Suppression changes also need confirm: true. Connected through an API key, those actions are allowed, because only admins can create keys. Guardrail settings themselves can only be edited in the dashboard, not through the connector.

A safe order for the AI

  1. get_send_policy to read the cap, quiet hours, blocked domains and how many sends are left today.
  2. check_send_readiness for a go, caution or no-go on your sending domain and recent bounce and complaint rates.
  3. check_compliance and score_email on the draft. Fix errors.
  4. send_campaign with dryRun: true. Read the numbers and the blocked list.
  5. After you agree, send_campaign with dryRun: false, confirm: true and an idempotencyKey.
  6. get_campaign_performance afterwards to see opens, clicks, bounces and complaints.

Related