Postboy Help

Message Lifecycle

Purpose

Every message in Postboy follows a predictable journey from creation to its final outcome. Understanding this lifecycle is essential for:

  • Debugging message flow

  • Implementing middleware correctly

  • Reasoning about when a message can be cancelled or blocked

  • Distinguishing the paths taken by different message types

The lifecycle applies uniformly to PostboyGenericMessage, PostboyCallbackMessage<T>, and PostboyExecutor<T>, though each type has its own specific completion behavior.

Lifecycle stages

A message passes through five distinct stages:

  1. Creation

  2. Publication

  3. Subscription Resolution

  4. Processing

  5. Outcome

Each stage provides an opportunity for middleware to intervene or for the bus to alter the flow.

1. Creation

The message begins as an instance of a concrete class that extends PostboyMessage. At this point:

  • The message id is established via the class's static ID field

  • Payload data is set through the constructor

  • Metadata may be attached using setMetadata()

Example:

const message = new UserCreatedEvent('user-123') .setMetadata({ tags: new Set(['user', 'audit']) });

The message is now ready but has not yet entered the bus.

2. Publication

Publication is the act of handing the message to Postboy. Depending on the message type, this is done via:

  • postboy.fire(message) for generic messages

  • postboy.fireCallback(message) for callback messages

  • postboy.exec(executor) for executors

At this moment:

  • The bus validates that the message type is not locked (if locked, publication is rejected)

  • The message identity is recorded for subscription resolution

  • Middleware configured for pre‑processing is invoked

If pre‑processing middleware decides to cancel the flow, the message never reaches subscribers or handlers. Otherwise, it proceeds to the next stage.

3. Subscription Resolution

For generic and callback messages, the bus resolves which subscribers are registered for the message's id. This resolution considers:

  • Subscriptions bound directly to the message type

  • Namespace scoping (if applicable)

  • Whether the subscription is active or temporarily paused

For executors, this stage resolves the registered handler function for the given executor id.

If no subscriber or handler is found, the message is effectively ignored (a silent no‑op, though middleware may still observe it).

4. Processing

This is where the actual work defined by subscribers or handlers takes place.

For PostboyGenericMessage

Each subscriber receives the message and may perform side effects. The order of subscriber invocation is not guaranteed unless explicitly configured. Subscribers can be synchronous or return an Observable/Promise.

For PostboyCallbackMessage<T>

The designated handler processes the message and must finish it by calling message.finish(result). The response is then delivered back to the caller through the Observable returned by fireCallback().

For PostboyExecutor<T>

The registered executor function is invoked with the executor instance. It may return a result synchronously or asynchronously. The bus waits for completion (or cancellation) before resolving the Promise from exec().

During processing, in‑flight middleware can still observe or even cancel the operation. For example, a timeout middleware could abort a long‑running executor.

5. Outcome

The lifecycle ends in one of three ways:

Completion

The message was handled successfully, and the flow terminates normally.

  • For generic messages: all subscribers have been invoked (or the observable has completed).

  • For callback messages: finish() was called and the response emitted.

  • For executors: the handler returned a value (or void) without error.

Cancellation

The message was interrupted by middleware or an explicit bus operation before reaching its natural end. Cancellation can happen:

  • During pre‑processing (before subscription resolution)

  • During processing (e.g., a timeout or manual intervention)

When cancelled, subscribers or handlers are not executed (or, if already running, they may be interrupted depending on the implementation).

Blocking (Prevention)

A message type can be locked administratively. When locked, any attempt to publish it is rejected immediately. This is useful for:

  • Temporarily disabling a feature

  • Preventing duplicate processing during maintenance

  • Controlling message flow during deployment

Locking operates at the type level, not the instance level, and is managed via Postboy's internal registry.

Lifecycle by message type (summary)

Stage

PostboyGenericMessage

PostboyCallbackMessage<T>

PostboyExecutor<T>

Publication

fire(message)

fireCallback(message)

exec(executor)

Resolution

Finds subscribers by id

Finds handler by id

Finds executor function by id

Processing

One‑to‑many broadcast

Single handler, calls finish()

Direct invocation of registered function

Outcome

Completion or cancellation

Completes with response or cancellation

Returns result or cancellation

Interaction with middleware

Middleware participates in the lifecycle at two key points:

  • Before subscription resolution – can cancel or modify the message

  • During processing – can observe, enrich, or cancel the flow

Because all messages inherit from PostboyMessage, middleware can operate on any message type without needing to know its specific contract.

Why this lifecycle matters

A clearly defined lifecycle:

  • Makes middleware behavior predictable

  • Helps developers understand when and why a message might not reach its target

  • Provides clear extension points for instrumentation, logging, and error handling

  • Separates concerns: creation logic lives in application code, flow control in middleware, and business logic in subscribers/handlers

In short

The Postboy message lifecycle takes a message from its creation, through publication and subscription resolution, into processing, and finally to one of three outcomes: completion, cancellation, or blocking. This structured path ensures that all communication remains consistent, observable, and controllable—regardless of which message contract you choose.

03 мая 2026