Inboxes
A function can call out; nothing can call in to a browser, a CLI session, or an agent running in someone's cloud. An inbox is a throwaway URL that accepts a POST from anyone and holds it until you read it — an inbound address for things that don't have one.
The core idea
rusted inbox new stripe-data --ttl 2m
→ https://rusted.sh/inbox/435007f5f71dc851a66e39aedb5b4e43ebdadb5ae12c878a
anyone with this URL can POST to it; reading needs your key · expires in 120s
The write address and the read handle are different things on purpose. The URL is unguessable and grants exactly one capability: writing. Reading is by name and needs your key — so handing the URL to Stripe never hands over what Stripe sent. That separation is what makes it safe to paste a receiving URL into someone else's dashboard.
Reading, three ways
rusted inbox get stripe-data # CLI — also: inbox list, inbox rm
From inside a deployed function, scoped to whoever deployed it — naming another account's inbox finds nothing:
export default async function handler(request, context) {
const messages = await context.inbox.get("stripe-data");
return context.json({ received: messages.length });
}
Or over MCP as inbox_create and inbox_read — how an agent uses it: create, hand out the URL, poll until something lands.
How arrivals accumulate
| flag | behavior |
|---|---|
--store append | Keep every message (default — the mode that can't silently lose one). |
--store upsert | Keep only the most recent — right for a single value like an OAuth code. |
--drain | Delete the inbox on the first read that finds something, like taking a message off a queue. At-most-once: if the read response is lost in flight, the message is gone — fine for a code you can request again, wrong for a payment event. |
Lifecycle and bounds
The TTL runs from creation and is never extended by activity — sliding expiry would let anyone holding the write URL keep the inbox alive forever. Expired, drained, and never-existed all answer the same 410 Gone, so probing addresses reveals nothing, and well-behaved webhook senders stop retrying. Alive-but-empty answers 200 with no messages, so a poller can tell "nothing yet" from "too late."
A public write endpoint is an unbounded write primitive unless bounded, so: 64 KB per message, 100 messages, 1000 accepted writes over an inbox's life, 24 h maximum TTL, and bodies must be valid UTF-8. Messages are served from memory and written through to Postgres on the same call — a restart loses nothing, and expiry deletes the payload rather than hiding it.
Use cases
- OAuth callbacks for things without an address — a CLI or agent starts a flow, registers the inbox URL as the redirect target, and polls for the code (
--store upsert --drain --ttl 2m). - Webhook capture — point Stripe/GitHub at an inbox and let a function read the backlog on its own schedule, instead of hosting an always-on receiver.
- "Notify me when it's done" — hand a long-running job an inbox URL; whoever finishes POSTs the result.
- Human-in-the-loop — an agent creates an inbox, sends someone the URL (a form, an approval), and continues when the answer arrives.
rusted