/api/qmail/local/can-send

GET

Preflight whether the payment wallet can fund a send: estimated storage fees (per-server stripe formula) plus required inbox fee. Local wallet/pool only — does not contact RAIDA for payment. Handler: api_handle_qmail_can_send.

Description

Computes wallet_required = sum of per-server upload fees + required inbox fee, then compares against wallet balance plus locked pool value. Returns can_send, a stable reason code, and a user-facing message.

Storage-fee estimate uses the same interim formula as CMD 70 / CMD 75 (0.01 CC per MiB of per-server stripe data, ceil to the next 0.01 CC, welfare free under the configured allowance, default 1 MiB). Prefer files= (file sizes in bytes) when available; it mirrors orchestrator striping math. Fallback is stripe_bytes (same stripe size on every server). If neither is present, upload fees are estimated as zero (welfare / free upload).

Inbox fee: explicit inbox_fee / required_inbox_fee if provided; otherwise max fee from to / cc / bcc contact rows (default when unknown). If still zero (compose-level check with no recipients), the configured default inbox fee is used so an empty Default wallet is blocked before the user writes a message.

Healthy-server count vs live send

estimated_server_count comes from qmail_health_get_healthy_servers (fallback: configured server count, then 1). Preflight does not apply the orchestrator’s cooldown filter or expand-to-configured when healthy < required data stripes. Default upload_lockers equals this estimate; override with upload_lockers or servers.

Parameters

ParamTypeRequiredDescription
wallet_path string No (default Default) Wallet name (e.g. Default) or full path; wallet is accepted as an alias. Must exist. The Mail wallet cannot fund payments when a fee is due (can_send: false, reason: MAIL_WALLET_NOT_ALLOWED).
upload_lockers integer No Number of upload lockers / servers for the fee sum (0…QMAIL_MAX_SERVERS). Alias: servers. Default: estimated_server_count.
files string (CSV) No Comma-separated file sizes in bytes (e.g. 2621440,1048576). When present and non-empty, wins over stripe_bytes. Strict unsigned decimal CSV: no signed numbers, no scientific notation, no empty items, no leading/trailing/consecutive commas. Max 64 files. Empty files= is HTTP 400.
stripe_bytes integer No Per-server stripe size in bytes when files is absent. Same fee applied to every locker. Strict unsigned parse: leading +/- rejected (HTTP 400). Absent or unused → zero stripe fee (welfare estimate).
inbox_fee number / integer No Explicit required inbox fee in decimal CC (e.g. 10, 0.25) or legacy whole-CC integer. Alias: required_inbox_fee. When set, recipient lists are not used for fee amount.
to, cc, bcc string No Recipient address lists (comma/semicolon-separated) used only when inbox_fee is not supplied. Handler takes the max contact inbox fee (or default for unknown contacts). Counts appear as recipient_count / unknown_fee_count.

Response

{
  "command": "can-send",
  "success": true,
  "request_id": "…",
  "can_send": true,
  "reason": "OK",
  "message": "Ready to send mail",
  "required_upload_lockers": 10,
  "estimated_server_count": 10,
  "required_inbox_fee": 10,
  "upload_locker_fee_cc": 0.03,
  "default_inbox_fee_cc": 10,
  "inbox_fee_explicit": false,
  "recipient_count": 0,
  "unknown_fee_count": 0,
  "upload_available": 0,
  "inbox_fee_available": 3,
  "inbox_fee_available_for_amount": 3,
  "wallet_required": 10.3,
  "locked_available_value": 30,
  "locked_value": 30,
  "wallet_balance": 114.5,
  "combined_wallet_value": 144.5,
  "low_balance": false
}
FieldTypeDescription
commandstringAlways "can-send".
successbooltrue when the preflight ran (not an HTTP error).
request_idstringCorrelation id for logs (always present).
can_sendbooltrue when combined wallet + locked value ≥ wallet_required (or free send on Mail wallet).
reasonstringStable code: OK, NO_SEND_FUNDS, MAIL_WALLET_NOT_ALLOWED.
messagestringUser-facing explanation.
required_upload_lockersintLocker count reported for the send (minimum 1, even when upload_lockers=0 zeroes the fee sum).
estimated_server_countintHealthy-server count. Used as the default when upload_lockers/servers is not supplied; an explicit override also drives the files= striping math.
required_inbox_feenumberExact decimal CC inbox fee included in the total.
upload_locker_fee_ccnumberAverage per-server upload fee (decimal CC). Historical field name; not a fixed whole-CC charge.
default_inbox_fee_ccnumberConfigured default inbox fee (decimal CC).
inbox_fee_explicitbooltrue when inbox_fee / required_inbox_fee was supplied.
recipient_countintResolved recipients from to/cc/bcc (0 when fee is explicit or lists omitted).
unknown_fee_countintRecipients that fell back to the default inbox fee.
upload_availableintPool rows typed upload currently available (often low between per-send funding).
inbox_fee_availableintInbox-fee lockers available in the pool.
inbox_fee_available_for_amountintAvailable inbox-fee lockers funded at or above required_inbox_fee.
wallet_requirednumberExact decimal CC total: sum of per-server upload fees + inbox fee.
locked_available_valuenumberLocked pool value in AVAILABLE status (decimal CC).
locked_valuenumberTotal locked pool value (decimal CC).
wallet_balancenumberBank + fracked balance (decimal CC).
combined_wallet_valuenumberwallet_balance + total locked value.
low_balanceboolUI hint from pool status (replenish may starve).

Worked fractional example

Request: wallet_path=Default&stripe_bytes=2621440&upload_lockers=10&inbox_fee=10

  • 2 621 440 bytes = 2.5 MiB per server → 2.5 × 0.01 = 0.025 → ceil to 0.03 CC per server.
  • Upload total: 10 × 0.03 = 0.3 CC.
  • Inbox fee: 10 CC (explicit).
  • wallet_required = 10.3; upload_locker_fee_cc = 0.03; inbox_fee_explicit = true.
{
  "command": "can-send",
  "success": true,
  "request_id": "…",
  "can_send": true,
  "reason": "OK",
  "message": "Ready to send mail",
  "required_upload_lockers": 10,
  "estimated_server_count": 10,
  "required_inbox_fee": 10,
  "upload_locker_fee_cc": 0.03,
  "default_inbox_fee_cc": 10,
  "inbox_fee_explicit": true,
  "recipient_count": 0,
  "unknown_fee_count": 0,
  "upload_available": 0,
  "inbox_fee_available": 3,
  "inbox_fee_available_for_amount": 3,
  "wallet_required": 10.3,
  "locked_available_value": 30,
  "locked_value": 30,
  "wallet_balance": 114.5,
  "combined_wallet_value": 144.5,
  "low_balance": false
}

With files=2621440 instead of stripe_bytes, per-server stripe size is derived from orchestrator striping (data_stripes = max(1, server_count - 1)), then the same fee formula is applied to every server.

Errors

HTTPMessage
400Invalid wallet_path
400Invalid upload_lockers parameter
400Invalid inbox_fee parameter
400Invalid files parameter — empty files=, signed numbers (+/-), scientific notation, empty CSV items, overflow, or fee estimate failure
400Invalid stripe_bytes parameter — signed input, non-decimal junk, or overflow
404Wallet not found
500Failed to check send funding

Mail-wallet payment rejection is a successful JSON body (success: true, can_send: false, reason: MAIL_WALLET_NOT_ALLOWED), not HTTP 400.

Example

# Stripe-based fractional estimate (2.5 MiB/server × 10 + 10 CC inbox)
curl "http://localhost:8081/api/qmail/local/can-send?wallet_path=Default&stripe_bytes=2621440&upload_lockers=10&inbox_fee=10"

# File sizes CSV (strict unsigned decimals)
curl "http://localhost:8081/api/qmail/local/can-send?wallet_path=Default&files=2621440,1048576&upload_lockers=10"

# Compose-level check (default inbox fee, zero storage estimate)
curl "http://localhost:8081/api/qmail/local/can-send?wallet_path=Default"

Related