Skip to content
Art2link ESB HomeDocumentationBlogContact
Updated May 5, 2026
Tutorials/Routing & pub-sub/Regional order routing

Regional order routing

Acme sells into three regions and runs a warehouse in each: Europe (EMEA), the Americas (AMER), and Asia-Pacific (APAC). Every warehouse has its own fulfilment system, and the rule is simple. An order is fulfilled by the warehouse for its region. An order placed in Hamburg ships from the EMEA warehouse, never from Dallas.

So every incoming order has to be read, matched to its region, and handed to that region’s system. And sometimes an order will arrive carrying a region nobody recognises, a typo or a brand-new sales territory. That one must be set aside for a person, never guessed at and shipped to the wrong continent.

Each order goes to its region’s warehouse New order ship to: Hamburg Region: EMEA Match the region EMEA warehouse Hamburg order ships here AMER warehouse Dallas, not this order APAC warehouse Asia-Pacific Region not recognised held for a person, never shipped

This is a textbook content-based router: the message itself carries the value that decides where it goes. Here is how Art2link ESB builds it.

Every order arrives as a canonical Order on the bus (the shared message backbone). The order already has a region field in its payload, but a field buried in the body is not something the bus can route on directly. So we promote it: we lift region into a named token, {{Promoted.Order.Region}}, that subscriptions can read without parsing the body. You promote only what you route on, nothing more.

Each region then gets its own send port, an API Caller pointed at that region’s fulfilment endpoint, and each port subscribes to a single value:

EXPRESSION
{{Promoted.Order.Region}} == "EMEA"
EXPRESSION
{{Promoted.Order.Region}} == "AMER"
EXPRESSION
{{Promoted.Order.Region}} == "APAC"

Because the promoted token names its message type, each expression already restricts itself to Order messages. Opening a fourth region is a new send port with a new value, the storefront and the existing ports never change.

Order Region promoted route on Region == "EMEA" == "AMER" == "APAC" else API Caller, EMEA fulfilment API Caller, AMER fulfilment API Caller, APAC fulfilment dead-letter + ops alert

When it fails. Two distinct failures here. An order whose Region matches no subscription is held in the Suspended state, the built-in dead-letter channel, the moment a typo or a new sales territory produces "UK&I"; nothing ships from the wrong continent and nothing is dropped. A delivery failure against a regional endpoint is the usual story: exception type FulfilCallFailed on each API port, one O365 Mail operations subscription, replay from tracking when the endpoint recovers.

EXPRESSION
{{Message.MessageType}} == "FulfilCallFailed"
Watch the unmatched orders. Suspended messages wait for an operator, make sure someone is told they exist. Turn on Activity Notifications for the Application (On Error Only) or add an explicit catch-all branch, so an unknown region is a same-day fix, not a week-old mystery.

Build it, step by step. The steps run in dependency order: every object is created before the object that selects it.

Before you start. Everything below lives inside the same Application as the intake build. If you are starting clean, create AcmeOrders with Namespace AcmeOrders under Applications and select it, so the Promotion and every port below are created in its scope.
1
Step One
Create the message types
The two types

Under the application’s Message types, create the two types this flow routes on. A message type is a name plus a format, no schema required:

NameFormatPurpose
OrderJSONthe canonical order the router fans across the regions
FulfilCallFailedJSONthe exception type a failed fulfilment call re-publishes under, payload intact (Step 5)
Promotion on Order

Promotions live on the message type, so add it while you are here: lift the region field into a named token the bus can route on. Subscriptions are plain strings, they bind {{…}} tokens but never evaluate a body path; from now on every published order exposes {{Promoted.Order.Region}} to subscriptions.

EXPRESSION
Promotion name : Region
Source         : $.region        (the canonical Order payload field)
Exposed as     : {{Promoted.Order.Region}}
A sample Order

The payload the Step 6 test posts, region field included:

JSON
{
  "orderNumber": "SO-10515",
  "region": "AMER",
  "customer": { "id": "C-1148" },
  "total": 740.00
}

2
Step Two
Create the Authentications

Five external parties, five credentials. Under the application’s Authentications, the dialog asks for a Name, the Adapter it pairs with, and a Definition, the credential shape that adapter offers, with the Application preset; the Definition decides the config section that follows.

Storefront credential, for the API Listener
SettingValue
NameStorefrontToken
AdapterAPI Listener
DefinitionAPI Listener Token
Token (Partner Config)the secret the storefront will present on every call
Regional fulfilment APIs, for the API Caller

Each region’s fulfilment API has its own endpoint and its own account, so create three credentials, one per regional port; they differ only in name and values. The Definition is whatever that region’s API demands: API Bearer Token (Login URL, Refresh Token URL, Token JSON Path, Refresh Token JSON Path, Username, Password) or API Basic Authentication (Username and Password).

SettingValue
NameFulfilEMEAAuth / FulfilAMERAuth / FulfilAPACAuth
AdapterAPI Caller
DefinitionAPI Bearer Token or API Basic Authentication
Credential fieldsthat region’s account values, per the chosen Definition
Alert mailbox, for O365 Mail
SettingValue
NameO365Ops
AdapterO365 Mail Sender
DefinitionMicrosoft Graph
Tenant Id / Client Id / Client Secret (Graph AuthConfig)an app registration with Mail.Send granted

3
Step Three
Create the receive port
General

If the online order intake build already lives in this Application, skip this step: its API Listener and WebOrderToOrder map are the intake. On an empty environment, stand up a minimal edge instead. This walkthrough starts where a canonical Order is already on the bus, so the test rig publishes one directly, no map. Create a receive port WebshopOrders. (The dropdowns can also create types and maps inline; building the dependencies first keeps each dialog a selection.)

SettingValue
AdapterAPI Listener
WayOne
AuthenticationStorefrontToken, from Step 2
Message Type FallbackOrder, from Step 1
Declare the routing headers

Declare the two matching headers that keep this Listener unique on the shared ingress (routing on a shared ingress covers the full rule):

HeaderValueWhy
AppAcmeOrdersthe Application’s namespace, narrows the request to this Application
PurposeOrderIntakekeeps this Listener unique if the App later gains a second one (e.g. a stock lookup)

The Step 6 test then posts orders, one per region, straight to the shared ingress with those two headers.


4
Step Four
Create one fulfilment port per region
General

Create three send ports on the API Caller, FulfilEMEA, FulfilAMER, FulfilAPAC, each with its region’s endpoint URL and its own Authentication, from Step 2. On every port, Method POST and Outbound map None: the canonical Order JSON passes as the request body unchanged, {{Message.Body}}.

PortEndpoint URL
FulfilEMEAhttps://eu.fulfil.example/orders
FulfilAMERhttps://us.fulfil.example/orders
FulfilAPAChttps://ap.fulfil.example/orders
Subscriptions, one per port

Each regional API Caller subscribes on its own value:

EXPRESSION
FulfilEMEA :  {{Promoted.Order.Region}} == "EMEA"
FulfilAMER :  {{Promoted.Order.Region}} == "AMER"
FulfilAPAC :  {{Promoted.Order.Region}} == "APAC"

5
Step Five
Wire the failure path
Set the Exception Message Type

On all three fulfilment ports, set Exception Message Type, on the port’s General step, to FulfilCallFailed, from Step 1.

Create the OpsAlert send port

Create the OpsAlert O365 Mail port subscribing on {{Message.MessageType}} == "FulfilCallFailed":

SettingValue
AuthenticationO365Ops, from Step 2
Fromnoreply@acme.example
Toops@acme.example
SubjectRegional fulfilment call failed
Catch the no-match case

For the no-match case, turn on Activity Notifications (On Error Only) for the Application so a suspended order emails someone too.


6
Step Six
Start the ports and test
Start in dependency order

Everything you configure is live the moment you save it, there is nothing to deploy. Start the send ports first, then the receive ports.

Turn on tracking

Set Tracking to Enabled + Body on every port the flow touches, so you can walk each run step by step, with its message body, in tracking.

Post one order per region

Post one order per region and confirm in tracking that each leaves through the right port.

Send an unknown region

Post an order whose canonical region is "XX" (on the full intake build, the storefront field ship_region carries it there) and confirm it suspends (the built-in dead-letter channel) rather than shipping from the wrong continent. Then Terminate the suspended message and post a corrected order.

Turn bodies off in production. Enabled + Body is the most expensive tracking level, extra processing and database space, and it records every payload including successful runs. Once the flow is proven, set the ports to Only on Error instead: failures still capture the full body for diagnosis, while healthy runs are not recorded.