Transports
What is a transport?
Section titled “What is a transport?”A transport is how postal crosses an execution boundary — same-origin tabs via BroadcastChannel, iframes and workers via MessagePort. Without a transport, a postal bus is local to its JavaScript context. With one, publishes on one side automatically appear on the other.
Transports are symmetric: each side registers its own transport, and postal handles the rest — echo prevention, filtering, and local dispatch.
The Transport interface
Section titled “The Transport interface”You rarely implement this directly — the transport packages do it for you. The interface matters when you’re building a custom transport (see below).
Registering a transport
Section titled “Registering a transport”addTransport wires the transport into postal’s outbound hook and starts listening for inbound envelopes. It returns an idempotent remove function.
Filtering
Section titled “Filtering”You can restrict which envelopes a transport forwards using a TransportFilter. Unfiltered transports forward everything.
Both channels and topics are optional and additive — an envelope must pass both to be forwarded. "reply" envelopes (RPC responses) always bypass the filter, because they need to complete a round-trip that already matched on the way out.
Echo prevention
Section titled “Echo prevention”When postal forwards an outbound envelope, it stamps it with the local instanceId in the source field. On the receiving side, any envelope whose source matches the local instanceId is silently dropped before dispatch. This prevents a message from bouncing back and triggering a second dispatch in the originating context.
You don’t need to do anything — it’s automatic. It’s documented here so you know why envelope.source exists.
Available transport packages
Section titled “Available transport packages”postal-transport-messageport
Section titled “postal-transport-messageport”Point-to-point transport for iframes and dedicated workers. Uses a MessageChannel with a handshake (SYN/ACK) to ensure both sides are ready before messages flow.
Iframe — parent side:
Iframe — child side:
Worker — main thread:
Worker — inside the worker:
postal-transport-broadcastchannel
Section titled “postal-transport-broadcastchannel”Many-to-many transport for same-origin tabs and windows. Uses the browser’s BroadcastChannel API — no handshake, no configuration. Every tab that registers the transport is immediately in the mesh.
Writing a custom transport
Section titled “Writing a custom transport”If you need a different channel (WebSocket, SharedWorker, postMessage with a custom protocol), implement the Transport interface directly:
The contract:
sendis called with a shallow copy of the envelope already stamped withsource. Don’t mutate it.subscribemust return an unsubscribe function. postal calls it when the transport is removed.disposeis called once on removal or reset. Make it idempotent.
Cleanup and test isolation
Section titled “Cleanup and test isolation”resetTransports() removes all registered transports and calls dispose() on each:
resetChannels() calls resetTransports() internally, so a full bus reset also cleans up transports. Use resetTransports() directly when you want transport-only teardown without clearing channels and subscribers.