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

flagbehavior
--store appendKeep every message (default — the mode that can't silently lose one).
--store upsertKeep only the most recent — right for a single value like an OAuth code.
--drainDelete 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.