Skip to content

Mailboxes ​

Every actor owns a mailbox and drains it one message at a time, which is how single-threaded processing per actor works. A mailbox is never shared between actors.

Pass a mailbox in SpawnOptions. The default is new UnboundedMailbox(). Mailboxes are live objects: they cannot ride a Props spawn.

ts
await system.spawn("worker", new Worker(), {
  mailbox: new BoundedMailbox(128),
});

enqueue returns null on accept, or an Error on reject. Compare sentinels by identity: err === ErrMailboxFull. A rejected user message is published as a dead letter. After dispose (the actor has stopped), enqueue returns ErrMailboxDisposed and dequeue returns undefined.

You can implement Mailbox yourself. Document any departure from FIFO.

FIFO ​

ClassBoundNotes
UnboundedMailboxNoDefault. Growable ring. Idle mailboxes at or below 64 slots release the buffer.
BoundedMailboxYesFixed ring. Constructor new BoundedMailbox(capacity) throws RangeError unless capacity is a positive integer. Full → ErrMailboxFull (non-blocking; the message is not stored).
UnboundedSegmentedMailboxNoLinked segments of 256 messages. Amortized O(1), no per-message allocation. A drained mailbox hands memory back. Use for high-throughput, bursty fan-in.

Fair ​

UnboundedFairMailbox gives each sender its own sub-queue. Messages from the same sender stay FIFO; the consumer round-robins one message per sender per turn, so a chatty sender cannot starve quieter peers.

ts
new UnboundedFairMailbox(); // key = sender path; no-sender messages share one queue
new UnboundedFairMailbox((msg) => msg.sender?.name() ?? "");

SenderKeyFunc is (msg: ReceiveContext) => string. A sender's sub-queue is created on first use and dropped when it drains.

When raw throughput of a single hot sender matters more than fairness, use UnboundedMailbox.

Priority ​

PriorityFunc is (msg1, msg2) => boolean: return true when msg1 should run before msg2. The function receives the message payloads (ctx.message), not the contexts.

ClassBoundEqual-priority order
UnboundedPriorityMailboxNoUnspecified
UnboundedStablePriorityMailboxNoFIFO (arrival sequence)
BoundedPriorityMailboxYesUnspecified
BoundedStablePriorityMailboxYesFIFO
ts
new UnboundedStablePriorityMailbox((a, b) => (a as Job).priority > (b as Job).priority);
new BoundedPriorityMailbox(64, (a, b) => /* ... */);

Bounded constructors throw RangeError unless capacity is a positive integer. Full → ErrMailboxFull. enqueue / dequeue are O(log n).

Stable variants stamp each message with a monotonic sequence and are slightly slower (up to two priority calls per comparison).

Released under the MIT License.