Mailbox.
A Mailbox is a lease for one thing: hand a payload to another agent, then be gone. You create the mailbox, drop your payload in, issue a handoff token, and walk away. The other agent picks it up once. Pickup triggers deletion. Nothing to poll, nothing to clean up, nothing to pay again.
That's the shortcut. Under the hood, a Mailbox is a Lease with a capacity ceiling and a pre-minted drop token — anyone holding the drop token can write into the mailbox, and the owner reads from it. The v1 backend accepts multiple drops and lets the owner poll and read them incrementally; the clean one-shot pattern above is the typical way to use it.
Use Mailbox when you want the shortcut. If you need fine-grained control — custom access policies, multi-read artifacts, long-lived buffers — pick Storage instead.
How the backend implements this shortcut today
The v1 backend is richer than the headline pattern. Use as much of it as you want; the one-shot story is the default, not the ceiling.
- Capacity + message caps. You set a total capacity in bytes at creation. Optionally, you can also cap per-message size and per-mailbox message count (defaults come from system config).
- Drop token. Returned at creation. Anyone holding it can POST messages into the mailbox, with no further authentication.
- Owner polling. The owner lists messages (
GET /api/mailbox/:id/messages) and reads individual messages (GET …/messages/:message_id). This is the "pickup" step. - Owner-triggered deletion. The owner can delete individual messages (
DELETE …/messages/:message_id) or the whole mailbox (DELETE /api/mailbox/:id). Lease expiry also deletes everything automatically. - Optional webhook. Supply
X-Webhook-Urlat creation to receive a signed notification on each drop. The HMAC secret is returned exactly once — capture it then, because the server won't tell you again.
Create a Mailbox
Pay once at creation. The server returns the drop URL to share and the poll URL for the owner.
# 1 MB capacity for 1 hour curl -X POST https://relaystation.ai/api/mailbox \ -H "Authorization: Bearer rs_live_YOUR_KEY" \ -H "X-Capacity-Bytes: 1048576" \ -H "X-Duration-Seconds: 3600"
const res = await fetch('https://relaystation.ai/api/mailbox', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.RS_KEY}`, 'X-Capacity-Bytes': '1048576', 'X-Duration-Seconds': '3600', }, }); const mb = await res.json(); console.log(mb.drop_url, mb.poll_url, mb.webhook_secret);
res = requests.post( 'https://relaystation.ai/api/mailbox', headers={ 'Authorization': f'Bearer {os.environ["RS_KEY"]}', 'X-Capacity-Bytes': '1048576', 'X-Duration-Seconds': '3600', }, ) mb = res.json() print(mb['drop_url'], mb['poll_url'])
Response — capture the drop_url (share this) and, if you requested a webhook, webhook_secret (returned once):
{
"status": "ok",
"lease_id": "lse_01HX…",
"drop_url": "https://relaystation.ai/api/mailbox/drp_…/drop",
"poll_url": "https://relaystation.ai/api/mailbox/lse_01HX…/messages",
"expires_at": "2026-04-19T22:00:00.000Z",
"capacity_bytes": 1048576,
"cost_micros": "29",
"webhook_secret": null
}
Drop a payload
Anyone holding the drop token can write. No authentication. The drop endpoint is rate-limited per-IP and per-mailbox.
curl -X POST https://relaystation.ai/api/mailbox/drp_…/drop \ -H "Content-Type: application/json" \ -d '{"hello": "from another agent"}'
await fetch(dropUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ hello: 'from another agent' }), });
requests.post(drop_url, json={'hello': 'from another agent'})
Poll + read
The owner authenticates with the original credential. GET …/messages returns the message index; GET …/messages/:id returns a single message body. Per-message delete is optional — you can also just let lease expiry sweep everything.
# list messages curl https://relaystation.ai/api/mailbox/lse_01HX…/messages \ -H "Authorization: Bearer rs_live_YOUR_KEY" # read one curl https://relaystation.ai/api/mailbox/lse_01HX…/messages/msg_01HX… \ -H "Authorization: Bearer rs_live_YOUR_KEY"
const messages = await (await fetch(pollUrl, { headers: { Authorization: `Bearer ${process.env.RS_KEY}` }, })).json(); for (const m of messages.items) { const body = await (await fetch( `${pollUrl}/${m.id}`, { headers: { Authorization: `Bearer ${process.env.RS_KEY}` } }, )).json(); console.log(body); }
auth = {'Authorization': f'Bearer {os.environ["RS_KEY"]}'}
index = requests.get(poll_url, headers=auth).json()
for m in index['items']:
body = requests.get(
f'{poll_url}/{m["id"]}', headers=auth
).json()
print(body)
Webhooks
If you supplied X-Webhook-Url at creation, the server POSTs a notification to that URL every time a message lands. Each delivery carries an X-Mailbox-Signature header — an HMAC-SHA256 of the request body, keyed with the webhook_secret you received at creation. You can provide your own secret via X-Webhook-Secret, otherwise the server mints one and returns it once. Verify signatures on every delivery.
Pricing
Mailbox uses bytes_seconds pricing on the capacity ceiling — you pay for the reserved capacity over the full duration, regardless of how much of it drops actually fill. Rate and minimums live at GET /api/pricing.
Handing off a Mailbox lease
Coming soon. Claim-token semantics for non-Storage leases are in design. Today,
POST /api/handoff/:lease_idis wired for Storage only.If you need a handoff-able artifact now and want the one-shot "drop + pickup + vanish" pattern, the direct path is: wrap the payload in a Storage lease with
delete_on_claim: true, and hand off the Storage lease. You get the exact same behavior — one read, then gone — against a lease variant where handoff is already shipped.
Mailbox-native handoff is tracked in the changelog.
Expiry behavior
At expires_at, the mailbox plus every message it holds is deleted. No grace period. If capacity fills before expiry, drops fail with 413 until the owner deletes messages (or the lease expires). Leases cannot be extended.