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": "[email protected]",
"to_address": "[email protected]",
"subject": "Hello",
"body": "Test email"
}'
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: "[email protected]",
to_address: "[email protected]",
subject: "Hello",
body: "Test email",
}),
},
);
{
"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"
}
Email
Get Email Quote
Get pricing quote for sending an email. Checks domain availability and returns costs.
POST
/
v1
/
tools
/
email
/
quote
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": "[email protected]",
"to_address": "[email protected]",
"subject": "Hello",
"body": "Test email"
}'
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: "[email protected]",
to_address: "[email protected]",
subject: "Hello",
body: "Test email",
}),
},
);
{
"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"
}
Authentication
RequiresX-Agent-ID header with your wallet address.
Request Body
string
Sender email address (e.g.,
[email protected]).If omitted, the server picks one from the agent’s domain pool via
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. [email protected] vs [email protected]).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.string | string[]
required
Recipient email address(es)
string
required
Email subject line
string
required
Email body content (plain text)
string
How the domain sends:
relay 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). See Send for the full semantics — the quote
locks the mode in, so omit mailbox_mode on /email/send to replay it.This parameter only chooses a mode while a domain is still
unprovisioned — either brand new, or owned with
provisioning_status: "unprovisioned". Once a domain is provisioned its own
mode always wins and this parameter is ignored, so on a provisioned domain
omitting it is not a way to avoid the fee, and relay is the default only
for a domain being set up for the first time. If the domain
reports mailbox_mode: "mailbox" on
/email/domains, every address that isn’t
already active there bills the fee on its first send, the canonical
agent@ included. from_mailbox / from_address don’t change that either
way. The mailbox_provisioning_fee on this quote’s response is always the
number you’ll pay.Response
string
Domain extracted from
from_addressboolean
Whether this is a new domain that needs registration
boolean
Whether the domain is available for registration
number
Cost to register the domain (USD)
number
Cost to send the email (USD)
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). 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).number
Total cost (registration + service + mailbox provisioning fees)
string
Quote ID to use in the send request
string
Domain status:
verified, provisioning, unprovisioned, or blockedstring
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.boolean
true when the server picked from the agent’s active pool.string
Non-blocking advisory. Surface it to the user and consider deferring:
pinned_domain_warming— a pinnedfrom_addresswhose domain is still warming (warmup score below threshold). Pinning bypasses the warmup gate.pinned_over_limit— a pinned domain already at/over itsdaily_send_limitfor today.
/email/send response. Pinned sends are never
blocked by warmup/limit — branch on warning to defer.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).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": "[email protected]",
"to_address": "[email protected]",
"subject": "Hello",
"body": "Test email"
}'
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: "[email protected]",
to_address: "[email protected]",
subject: "Hello",
body: "Test email",
}),
},
);
{
"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"
}
Bulk Usage
You can request a quote for sending independent emails to multiple recipients in a single request. Pass an array of strings toto_address.
The
service_fee will scale based on the number of recipients. The
registration_fee (if applicable) remains a one-time cost for the domain.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": "[email protected]",
"to_address": ["[email protected]", "[email protected]", "[email protected]"],
"subject": "Hello Team",
"body": "Weekly update..."
}'
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 = callerpool_status = 'active'(warming domains stay out of rotation until their reputation warmup score crossesWARMUP_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)
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 passfrom_address and the pool is skipped:
curl -X POST https://win.oneshotagent.com/v1/tools/email/quote \
-H "X-Agent-ID: <wallet>" -H "Content-Type: application/json" \
-d '{
"from_address": "[email protected]",
"to_address": ["[email protected]"],
"subject": "hi", "body": "..."
}'
Letting the server rotate
curl -X POST https://win.oneshotagent.com/v1/tools/email/quote \
-H "X-Agent-ID: <wallet>" -H "Content-Type: application/json" \
-d '{
"to_address": ["[email protected]"],
"subject": "hi", "body": "..."
}'
# → { "from_address": "[email protected]", "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)
ROTATION_DEFAULT_MAILBOX (env; default agent). For per-mailbox control, pass
from_address explicitly.
See GET /email/domains to inspect what’s
in your pool, and the pause /
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_feewill be0