Economics
Where the SOL goes: every lamport transfer in the protocol, traced from the
on-chain programs (mail_program/src/processor/), and what each participant
class pays and collects. Amounts marked ≈ are rent-exemption minimums —
a refundable, one-time deposit every Solana account needs to exist, sized to
the account’s byte length rather than to any price or value it represents
(see Closing accounts for what rent is and
how to reclaim it) — and vary with account size at current rent parameters.
This is a completely separate quantity from postage: postage is a price
the recipient chooses; rent is a storage deposit nobody chooses and
everybody gets back.
The stamp lifecycle
A stamp is prepaid postage for one email from one sender (“from” address) to one recipient wallet. Its value cycles through three accounts, then settles across four destinations:
- Buying (
AddStamps/CreateFrombox): anyone may fund the frombox for a (recipient, from) pair. Each stamp costs the frombox’srequired_postageplus a two-signature fee surcharge (STAMP_FEE_SURCHARGE_LAMPORTS= 10 000 = 2 × the 5 000-lamport base fee); the surcharge prefunds both settlement signature refunds (SendMail and DeleteMail). The whole amount transfers into the frombox account. A new frombox inheritsrequired_postagefrom the recipient mailbox’sdefault_postage; only the recipient may change it afterwards (UpdateFromboxrequires the recipient’s signature). Price changes do not revalue stamps already bought — value amortizes over the remaining stamp count. Third-party purchases additionally pay the per-stamp protocol fee straight to the postoffice (see “The per-stamp protocol fee” below); purchases paid by the recipient wallet itself are fee-free. - Sending (
SendMail): the signer must be the sender named in the email (or the active domain authority for the recipient’s domain — the MX operator’s wallet, for relayed mail). The signer pays the transaction fee and fronts the message account’s rent-exemption (≈0.003–0.006 SOL depending on address/cid sizes — a deposit sized to the account’s byte length, not to the postage price; see the note above) as a new deposit, separate from anything paid at the Buying step. One proportional share of the frombox’s value —(balance − frombox rent) / stamps— moves onto the message account alongside that rent, and one stamp burns. A frombox whose share rounds to zero still sends (the stamp count is the gate; the value is what settles later). - Settling (
DeleteMail): only the sender or the recipient may delete. The message balance is split, in order: the sender recovers the message rent + one signature fee, the delete signer recovers this transaction’s signature fee (both prefunded by the surcharge), the recipient’s domain authority collects the operator share (OPERATOR_SHARE_BPS= 10% of the remaining postage), and the recipient collects the rest.1 Postage only settles on delete; until then it sits parked on message accounts.
Try it yourself: a worked example
The script below re-derives the three steps above as plain arithmetic —
paste it into a browser console or run it with node (no dependencies,
no network access, nothing on-chain). Change postageLamports,
messageRentLamports, or thirdPartyBuyer at the top to try a different
scenario.
Money enters this example at two different points, not one: the buyer’s
purchase at Buying, and the sender’s rent deposit at Sending (the same
wallet, if the buyer is also the sender — but still two separate
transactions). That’s why the settlement totals add up to more than
buyerPays alone: the conservation check sums both inputs
(buyerPays + messageRentLamports) against everything paid out, and only
that combined total should balance.
Note: the default
postageLamportsbelow is priced at roughly what a single US first-class postage stamp costs (about $0.82 as of this writing), converted to lamports at the SOL/USD rate on the date this page was last updated ($81.16/SOL, 2026-07-06) — a deliberate nod to the stamp analogy, not a protocol default. Change it to see how the split scales.
// SithBit stamp economics calculator -- a hypothetical worked example.
// Pure JavaScript, no dependencies: paste into a browser console, or run
// with `node stamp-calculator.js`. All amounts are in lamports
// (1 SOL = 1,000,000,000 lamports).
// Protocol constants (from mail_model::constants)
const SIGNATURE_FEE_LAMPORTS = 5_000;
const STAMP_FEE_SURCHARGE_LAMPORTS = 10_000; // 2 x SIGNATURE_FEE_LAMPORTS
const POSTOFFICE_STAMP_FEE_LAMPORTS = 100_000; // waived if the buyer is the recipient
const OPERATOR_SHARE_BPS = 1_000; // 10% of postage, to the domain authority
const POSTAGE_ROUNDING_LAMPORTS = 1_000; // recipient payout rounds down to this
// --- Inputs: change these to try a different scenario ---
const postageLamports = 10_000_000; // ~0.01 SOL -- priced at ~$0.82, a US first-class stamp
const messageRentLamports = 4_000_000; // ~0.004 SOL -- rent-exemption sized to account bytes, independent of postage
const thirdPartyBuyer = true; // false = the recipient self-funds (fee waived)
const solUsdPrice = 81.16; // SOL/USD rate used for the $ comments below (2026-07-06)
function sol(lamports) {
return (lamports / 1_000_000_000).toFixed(9) + " SOL";
}
function usd(lamports) {
return "$" + ((lamports / 1_000_000_000) * solUsdPrice).toFixed(2);
}
// 1. Buying: AddStamps / CreateFrombox
const stampValue = postageLamports + STAMP_FEE_SURCHARGE_LAMPORTS;
const protocolFee = thirdPartyBuyer ? POSTOFFICE_STAMP_FEE_LAMPORTS : 0;
const buyerPays = stampValue + protocolFee;
console.log("== Buying: AddStamps / CreateFrombox ==");
console.log(`Buyer pays: ${buyerPays} lamports (${sol(buyerPays)})`);
console.log(` // ≈ ${usd(buyerPays)}`);
console.log(` -> frombox value: ${stampValue} lamports (${sol(stampValue)})`);
console.log(` -> protocol fee: ${protocolFee} lamports (${sol(protocolFee)})${thirdPartyBuyer ? " to the postoffice" : " (waived)"}`);
// 2. Sending: SendMail
const messageBalance = messageRentLamports + stampValue;
console.log("\n== Sending: SendMail ==");
console.log(`Sender fronts message rent: ${messageRentLamports} lamports (${sol(messageRentLamports)})`);
console.log(` // a new deposit -- separate from what the buyer paid above`);
console.log(`One stamp's value moves onto the message account: ${stampValue} lamports (${sol(stampValue)})`);
console.log(`Message account now holds: ${messageBalance} lamports (${sol(messageBalance)})`);
// 3. Settling: DeleteMail
const senderShare = messageRentLamports + SIGNATURE_FEE_LAMPORTS;
const deleteSignerShare = SIGNATURE_FEE_LAMPORTS;
const remainingPostage = messageBalance - senderShare - deleteSignerShare; // == postageLamports
const operatorShare = Math.floor((remainingPostage * OPERATOR_SHARE_BPS) / 10_000);
const recipientRaw = remainingPostage - operatorShare;
const recipientShare = Math.floor(recipientRaw / POSTAGE_ROUNDING_LAMPORTS) * POSTAGE_ROUNDING_LAMPORTS;
const postofficeResidue = recipientRaw - recipientShare;
console.log("\n== Settling: DeleteMail ==");
console.log(`Sender recovers: ${senderShare} lamports (${sol(senderShare)}) [rent + 1 signature fee]`);
console.log(` // ≈ ${usd(senderShare)}`);
console.log(`Delete signer recovers: ${deleteSignerShare} lamports (${sol(deleteSignerShare)}) [1 signature fee]`);
console.log(` // ≈ ${usd(deleteSignerShare)}`);
console.log(`Domain authority earns: ${operatorShare} lamports (${sol(operatorShare)}) [10% operator share]`);
console.log(` // ≈ ${usd(operatorShare)}`);
console.log(`Recipient collects: ${recipientShare} lamports (${sol(recipientShare)}) [the rest, rounded down]`);
console.log(` // ≈ ${usd(recipientShare)}`);
// 4. Conservation check
const totalIn = buyerPays + messageRentLamports;
const totalOut = senderShare + deleteSignerShare + operatorShare + recipientShare + protocolFee + postofficeResidue;
console.log("\n== Conservation check ==");
console.log(" // totalIn = buyerPays + messageRentLamports: money entered at TWO steps, not one");
console.log(`Total in: ${totalIn} lamports`);
console.log(`Total out: ${totalOut} lamports`);
console.log(totalIn === totalOut ? "Every lamport is accounted for." : "Mismatch -- check the math!");
Example output for the defaults above (a stranger buying one first-class-stamp-priced stamp, with the recipient later deleting the message to collect payment):
== Buying: AddStamps / CreateFrombox ==
Buyer pays: 10110000 lamports (0.010110000 SOL)
// ≈ $0.82
-> frombox value: 10010000 lamports (0.010010000 SOL)
-> protocol fee: 100000 lamports (0.000100000 SOL) to the postoffice
== Sending: SendMail ==
Sender fronts message rent: 4000000 lamports (0.004000000 SOL)
// a new deposit -- separate from what the buyer paid above
One stamp's value moves onto the message account: 10010000 lamports (0.010010000 SOL)
Message account now holds: 14010000 lamports (0.014010000 SOL)
== Settling: DeleteMail ==
Sender recovers: 4005000 lamports (0.004005000 SOL) [rent + 1 signature fee]
// ≈ $0.33
Delete signer recovers: 5000 lamports (0.000005000 SOL) [1 signature fee]
// ≈ $0.00
Domain authority earns: 1000000 lamports (0.001000000 SOL) [10% operator share]
// ≈ $0.08
Recipient collects: 9000000 lamports (0.009000000 SOL) [the rest, rounded down]
// ≈ $0.73
== Conservation check ==
// totalIn = buyerPays + messageRentLamports: money entered at TWO steps, not one
Total in: 14110000 lamports
Total out: 14110000 lamports
Every lamport is accounted for.
Per-participant flows
Recipient (mailbox owner)
| Pays | Collects |
|---|---|
Mailbox rent ≈0.0012 SOL (once, at CreateMailbox) | Stamp value of every received-then-deleted mail |
| Encryption-key account rent ≈0.005 SOL (once) | |
| Frombox funding for correspondents they allow-list | That same funding back, as mail arrives and is deleted |
The recipient is the protocol’s revenue side: strangers pay
required_postage per message, and pricing is the recipient’s spam
control (the CLI defaults new mailboxes to 1 SOL per stamp — unknown
senders are priced out until the recipient lowers the price for them).
When the recipient funds a correspondent’s frombox themselves, the value
round-trips back minus transaction fees — allow-listing costs only fees.
Accumulates SOL in proportion to mail from senders who paid their own
postage; net of their one-time rents otherwise.
Sender (wallet-holding)
Pays postage (the recipient’s price) plus the fee surcharge per stamp, and
fronts the message rent per send. On delete, rent and one signature fee
come back. Net cost per mail ≈ required_postage — the intended price of
communication. Spends SOL by design.
MX operator (runs sithbitd + the gRPC gateway)
For relayed mail, the operator’s domain-authority wallet is the on-chain
“sender”: it fronts the message rent and transaction fee per inbound
delivery, and recovers rent plus the surcharge-funded signature fee at
DeleteMail. On-chain revenue: the operator share — every settled
message for a mailbox on the operator’s active domain pays the authority
10% of the postage (OPERATOR_SHARE_BPS). At scale this turns relaying
into a revenue stream rather than a pure cost. Off-chain costs remain
(IPFS pinning, servers, RPC); the share offsets them on-chain.
The per-stamp protocol fee
The postoffice’s primary revenue: a flat fee per purchased stamp, collected at purchase time and transferred directly to the postoffice PDA.
- Amount:
stamp_fee_lamportsfrom the postoffice account (defaultPOSTOFFICE_STAMP_FEE_LAMPORTS= 100 000 = 0.0001 SOL), charged per stamp in the purchase. - Waiver: the fee is skipped iff the purchase’s fee payer is the
recipient wallet (
fee_payer == to_account). The payer is a required signer, so the check is unforgeable. This keeps the adoption path free: a mailbox owner who prices a correspondent’s frombox low and prefunds its stamps pays no protocol fee, and the recipient’s settlement payouts are untouched — mailbox owners perceive zero cost. Gift purchases by anyone else pay the fee. - Governance: the postmaster tunes the fee with
SetStampFee(sithbit postmaster set-stamp-fee <LAMPORTS>), hard-capped on-chain atMAX_POSTOFFICE_STAMP_FEE_LAMPORTS(1 000 000) so a compromised postmaster key cannot price-gouge purchasers. There is no on-chain read instruction — account state is world-readable; anyone can query the current fee with the ungatedsithbit postoffice fee. - Layout migration: postoffice accounts created before the fee field
(32-byte layout) still work everywhere — readers parse them with a
versioned load that applies the default fee, and the writers
(
SetStampFee,TransferPostmaster) grow the account in place (resize- rent top-up from the signing postmaster) on their next run.
Domain authorities
CreateDomain takes an explicit payer — either the postmaster or the
domain authority may fund the domain account’s rent plus the flat
DOMAIN_AUTHORIZATION_FEE_LAMPORTS (0.01 SOL) paid to the postoffice.
Either way the postmaster must sign: domain ownership is proven
off-chain (the authority’s pubkey in the domain’s DNS TXT record, checked
by the postmaster’s trusted agent), so on-chain authorization is always
the postmaster’s — authority-pays is a co-signed two-signature
transaction, never authority-alone. The domain records its rent_payer,
and CloseDomain (postmaster-signed) refunds the rent to whoever paid.
When the postmaster sponsors a domain itself, the fee is a wash — it lands
in the postoffice the postmaster can sweep. Deactivation remains a
separate, reversible toggle. In exchange, the authority earns the 10%
operator share on every settled message for its domain.
Alias holders
An alias costs its own account rent plus the flat ALIAS_FEE_LAMPORTS
(0.01 SOL) paid to the mail program’s postoffice — squatting a name now
has a price. Names remain globally unique, first-come, and never expire.
CloseAlias lets the holder (the wallet the alias points at) reclaim the
rent; the fee is not refunded.
Where SOL parks or strands
- Message accounts hold rent + stamp value until someone deletes. Both sides have an incentive to delete (sender: rent; recipient: postage), but nothing forces it.
- Every other account class now has a close path:
CloseFrombox(the recipient reclaims rent plus any residual stamp value — stamps never used),CloseMailboxandCloseKey(owner reclaims rent),CloseAlias(holder reclaims rent),CloseDomain(rent back to the recorded payer), andWithdrawPostoffice(the postmaster sweeps accumulated revenue). A closed mailbox’s undelivered message accounts stay open and settle individually viaDeleteMail; recreating the mailbox restarts message ids at zero, so new sends fail until those old message accounts are deleted — an availability nuisance, never a loss of funds.
Constants worth knowing
Protocol economics are consensus constants in mail_model::constants:
SIGNATURE_FEE_LAMPORTS (5 000, the real base fee — replacing the
deprecated 10 000-lamport fee-calculator default that overcharged buyers
~2×), STAMP_FEE_SURCHARGE_LAMPORTS (2 signatures),
DEFAULT_POSTAGE_LAMPORTS (1 SOL), OPERATOR_SHARE_BPS (1 000 = 10%),
DOMAIN_AUTHORIZATION_FEE_LAMPORTS (10 000 000 — charged by
CreateDomain), ALIAS_FEE_LAMPORTS (10 000 000 — charged by
CreateAlias), POSTOFFICE_STAMP_FEE_LAMPORTS (100 000 — the default
per-stamp protocol fee charged by AddStamps/CreateFrombox on
third-party purchases), and MAX_POSTOFFICE_STAMP_FEE_LAMPORTS
(1 000 000 — the hard cap on the postmaster-tunable fee).
On-chain CreateMailbox applies the 1-SOL DEFAULT_POSTAGE_LAMPORTS
spam-pricing floor when the instruction omits a price — a raw-instruction
caller must opt into a cheaper (or free) mailbox explicitly; the safety
margin is no longer client-side only.
Rent hygiene (implemented)
The follow-ups the original analysis recommended are now protocol behavior (see the “Economics” record in HANDOFF.md):
- Domain economics —
CreateDomaintakes an explicit payer (postmaster or authority; postmaster always signs), chargesDOMAIN_AUTHORIZATION_FEE_LAMPORTSto the postoffice, and records therent_payersoCloseDomaincan refund the rent to whoever funded it. - Close instructions —
CloseFrombox(recipient reclaims residue + rent),CloseMailbox/CloseKey(owner reclaims rent),CloseAlias(holder reclaims rent), andWithdrawPostoffice(the postmaster sweeps the postoffice balance above its rent-exempt minimum — without which the accumulated protocol fee revenue would be unspendable). - Alias fee — a flat
ALIAS_FEE_LAMPORTScharge on alias creation, CPI-transferred to the mail program’s postoffice, pricing out squatting. Waived when the payer is the postmaster (the fee would round-trip into the postoffice they sweep) — see bulk alias reservation.
Money is only half the picture: for what each participant must trust —
the MX operator’s sender authentication, the singleton postmaster, public
message metadata, and frombox custody (including that CloseFrombox
returns third-party-funded stamps to the recipient, not the buyer) — see
Trust assumptions and threat model.
-
The recipient’s payout is rounded down to the nearest
POSTAGE_ROUNDING_LAMPORTS(1 000 lamports); the sub-quantum remainder — at most 999 lamports per message — accrues to the postoffice. This is below the smallest price increment anyone quotes and isn’t something senders, recipients, or operators need to think about. ↩