> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oneshotagent.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Get Email Quote

> Get pricing quote for sending an email. Checks domain availability and returns costs.

## Authentication

Requires `X-Agent-ID` header with your wallet address.

## Request Body

<ParamField body="from_address" type="string" optional>
  Sender email address (e.g., `jn@yourdomain.com`).

  **If omitted**, the server picks one from the agent's domain pool via
  [domain rotation](#domain-rotation): LRU + per-day capacity + warmup-score
  gates over every `pool_status='active'` row the caller owns. The chosen
  address is echoed back as `from_address` in the response and **locked**
  into the quote — the matching `/email/send` replays it automatically, so
  the caller can either omit `from_address` on send or pass the same value.
  Passing a *different* address on send returns `400 from_address_mismatch`.

  **If provided**, the pool is bypassed and the server only checks domain
  ownership. Use this when you want to pin sends to a specific mailbox
  (e.g. `outreach@root.com` vs `agent@subdomain.root.com`).

  Pinning also **bypasses the warmup-score and `daily_send_limit` gates** —
  those are enforced only during rotation. If the pinned domain is still
  warming or over its daily cap the send still proceeds, but the response
  carries a non-blocking `warning` (`pinned_domain_warming` / `pinned_over_limit`).
  Reputation and limits are **per-domain**, shared by every mailbox on it, so
  multiple mailboxes are not a deliverability or capacity lever.
</ParamField>

<ParamField body="to_address" type="string | string[]" required>
  Recipient email address(es)
</ParamField>

<ParamField body="subject" type="string" required>
  Email subject line
</ParamField>

<ParamField body="body" type="string" required>
  Email body content (plain text)
</ParamField>

<ParamField body="mailbox_mode" type="string" optional>
  How the domain sends: `relay` (default) or `mailbox`. `mailbox` provisions a
  real dedicated mailbox per address and prices the one-time
  `mailbox_provisioning_fee` on this quote; `relay` is header-only send (fee
  `0`). Only applies when the domain is first set up. See
  [Send](/api-reference/email/send) for the full semantics — the quote **locks
  the mode in**, so omit `mailbox_mode` on `/email/send` to replay it.
</ParamField>

<Snippet file="audit-context-params.mdx" />

## Response

<ResponseField name="domain" type="string">
  Domain extracted from `from_address`
</ResponseField>

<ResponseField name="is_new" type="boolean">
  Whether this is a new domain that needs registration
</ResponseField>

<ResponseField name="available" type="boolean">
  Whether the domain is available for registration
</ResponseField>

<ResponseField name="registration_fee" type="number">
  Cost to register the domain (USD)
</ResponseField>

<ResponseField name="service_fee" type="number">
  Cost to send the email (USD)
</ResponseField>

<ResponseField name="mailbox_provisioning_fee" type="number">
  One-time fee (USD) charged the first time you send from an address that needs a
  real mailbox provisioned on the domain — the address is created and warmed on
  first use. This applies to **every** address, including the domain's default
  sender (`default_from`); it's `0` only once that address is already provisioned
  and **`active`** (see `addresses[]` on
  [`/email/domains`](/api-reference/email/domains)). An address whose mailbox is
  still `provisioning` or `failed` re-incurs the fee until it is `active`.

  Not every domain provisions a mailbox per address — relay/send-only domains send
  from any `from_address` without a per-address mailbox, so this fee is `0` for them
  regardless. **Trust this field on the quote**: `0` means nothing extra is billed;
  a non-zero value is the one-time charge.

  Surfaced here before payment and folded into `total_cost`. If you send from a new
  address without a quote that included this fee, `/email/send` returns
  `402 mailbox_provisioning_required` (re-quote with the exact `from_address`).
</ResponseField>

<ResponseField name="total_cost" type="number">
  Total cost (registration + service + mailbox provisioning fees)
</ResponseField>

<ResponseField name="quote_id" type="string">
  Quote ID to use in the send request
</ResponseField>

<ResponseField name="status" type="string">
  Domain status: `verified`, `provisioning`, `unprovisioned`, or `blocked`
</ResponseField>

<ResponseField name="from_address" type="string">
  Echoed only when the server picked the address (caller omitted it).
  Use it on `/email/send` or omit `from_address` on send — the value is
  locked into the quote and replayed automatically.
</ResponseField>

<ResponseField name="rotation" type="boolean">
  `true` when the server picked from the agent's active pool.
</ResponseField>

<ResponseField name="warning" type="string">
  Non-blocking advisory. Surface it to the user and consider deferring:

  * `pinned_domain_warming` — a pinned `from_address` whose domain is still
    warming (warmup score below threshold). Pinning bypasses the warmup gate.
  * `pinned_over_limit` — a pinned domain already at/over its `daily_send_limit`
    for today.

  The same field appears on the `/email/send` response. Pinned sends are never
  blocked by warmup/limit — branch on `warning` to defer.
</ResponseField>

<ResponseField name="error" type="string">
  `no_sending_domain` (HTTP 400) when you pass no `from_address` and own no
  rotation-eligible domain. **There is no shared fallback sender** — provision a
  domain (pass a `from_domain` to register) or pin one you own. `reason` is
  `empty` (no domains) or `exhausted` (all warming/over-cap).
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://win.oneshotagent.com/v1/tools/email/quote \
    -H "Content-Type: application/json" \
    -H "X-Payment-Proof: <your_x402_signature>" \
    -d '{
      "from_address": "hello@yourdomain.com",
      "to_address": "recipient@example.com",
      "subject": "Hello",
      "body": "Test email"
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://win.oneshotagent.com/v1/tools/email/quote",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Payment-Proof": paymentProof,
      },
      body: JSON.stringify({
        from_address: "hello@yourdomain.com",
        to_address: "recipient@example.com",
        subject: "Hello",
        body: "Test email",
      }),
    },
  );
  ```
</RequestExample>

<ResponseExample>
  ```json Response theme={null}
  {
    "domain": "yourdomain.com",
    "is_new": true,
    "available": true,
    "registration_fee": 10.0,
    "service_fee": 0.01,
    "mailbox_provisioning_fee": 0,
    "total_cost": 10.01,
    "currency": "USD",
    "quote_id": "quote_abc123",
    "status": "unprovisioned"
  }
  ```
</ResponseExample>

## Bulk Usage

You can request a quote for sending independent emails to multiple recipients in a single request. Pass an array of strings to `to_address`.

<Note>
  The `service_fee` will scale based on the number of recipients. The
  `registration_fee` (if applicable) remains a one-time cost for the domain.
</Note>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://win.oneshotagent.com/v1/tools/email/quote \
    -H "Content-Type: application/json" \
    -H "X-Payment-Proof: <your_x402_signature>" \
    -d '{
      "from_address": "hello@yourdomain.com",
      "to_address": ["alice@example.com", "bob@example.com", "charlie@example.com"],
      "subject": "Hello Team",
      "body": "Weekly update..."
    }'
  ```
</RequestExample>

## Domain Rotation

If the caller owns multiple domains (root or subdomains), `/email/quote`
without `from_address` rotates across them instead of hardcoding one. The
selector runs **in a single `FOR UPDATE SKIP LOCKED` query** so concurrent
quote calls never double-book the same row past its daily cap.

**Eligibility gates** (applied in SQL, AND-combined):

* `agent_id = caller`
* `pool_status = 'active'` (warming domains stay out of rotation until
  their reputation warmup score crosses `WARMUP_ACTIVE_THRESHOLD`)
* `daily_sent_count + recipientCount <= daily_send_limit` (per-domain
  per-UTC-day cap; resets lazily on the first send after midnight)
* `warmup_score IS NULL OR warmup_score >= MIN_ROTATION_SCORE` (default 60)

**Ordering**: `last_used_at ASC NULLS FIRST` (least-recently-used first).

**Side effects on success**: `last_used_at = NOW()`, `daily_sent_count +=
recipientCount`. Committed at quote time so concurrent quotes can't race
past capacity. If you bail without calling `/email/send`, the reservation
auto-releases (best-effort).

### Picking a specific domain

Just pass `from_address` and the pool is skipped:

```bash theme={null}
curl -X POST https://win.oneshotagent.com/v1/tools/email/quote \
  -H "X-Agent-ID: <wallet>" -H "Content-Type: application/json" \
  -d '{
    "from_address": "jn@oneshotagents.com",
    "to_address": ["someone@example.com"],
    "subject": "hi", "body": "..."
  }'
```

### Letting the server rotate

```bash theme={null}
curl -X POST https://win.oneshotagent.com/v1/tools/email/quote \
  -H "X-Agent-ID: <wallet>" -H "Content-Type: application/json" \
  -d '{
    "to_address": ["someone@example.com"],
    "subject": "hi", "body": "..."
  }'
# →  { "from_address": "agent@go.yourdomain.com", "rotation": true, "quote_id": "...", ... }
# (the rotated address is always a domain YOU own; if you own no eligible
#  domain the quote returns 400 no_sending_domain — there is no shared fallback)
```

The rotated address is always a domain **you own**; the mailbox local-part is
`ROTATION_DEFAULT_MAILBOX` (env; default `agent`). For per-mailbox control, pass
`from_address` explicitly.

See [`GET /email/domains`](/api-reference/email/domains) to inspect what's
in your pool, and the [pause](/api-reference/email/domain-pause) /
[resume](/api-reference/email/domain-resume) endpoints to take a domain
in or out of rotation without removing it.

## Notes

* Domain provisioning happens automatically during the send request
* Quote is valid for 1 hour
* For existing verified domains, `registration_fee` will be `0`
