Skip to content
Art2link ESB HomeDocumentationBlogContact
Updated May 5, 2026
Tutorials/File & batch patterns/Partner document relay

Partner document relay

A shipping carrier and a customs broker both work with Acme, and the carrier keeps producing dispatch documents, one per shipment, that the broker needs to clear goods. Nobody pushes them across. Instead Acme checks the carrier’s server every few minutes, picks up whatever is new, and delivers each document to the broker. At the same time Acme keeps its own copy of every document in a safe folder, so months later, if anyone asks "did you really send that one?", the answer is on file. The documents are passed along exactly as they arrive, nothing is changed. If the broker’s server is down, the file is set aside and delivered later, and the safe copy is still made either way.

So there are three parties and a clock. The carrier leaves documents in a pickup folder. A timer wakes up on a schedule, collects the new ones, hands each to the broker, and tucks a copy into the archive. The job is plain courier work, move the file, keep a receipt, and never lose track of what was sent.

Pick up each document, deliver it, keep a copy Carrier folder new dispatch docs every few min Pick up what is new Deliver to broker waits and retries if down Save a copy in the archive the receipt, made every time

You could write a script that does this, download, upload, delete, and it would work right up until the broker’s server is down at 03:00 and nobody can say which files made it across. Art2link ESB does the same courier work but puts it on the bus, the shared message backbone every flow publishes to and subscribes from, and that is what buys the retries, the audit trail, and the ability to send a document again on request. Here is how it is built.

The pickup leg mirrors the scheduled download. A Scheduler receive port (a receive port is the entry point that brings work onto the bus; on the Scheduler adapter it simply ticks on a timer) fires every 15 minutes. That tick wakes a two-way SFTP Caller send port (a send port delivers a message out to an external system, and an adapter is the connector for one kind of system, here a file server reached over SFTP), which downloads whatever is new in the carrier’s outbound directory. Each fetched document re-enters the bus as a DispatchDocument, a message type, which is just a name plus a format that lets subscriptions recognise the document, body untouched. Outbound filenames are derived from the payload itself: the document number is the one identifier that survives the trip, so both legs name the file the same way without any shared state.

Two send ports then subscribe to the same type, the delivery and the archive, the same move as the order fan-out. A subscription is just a rule that says which messages a port wants:

EXPRESSION
{{Message.MessageType}} == "DispatchDocument"
TEMPLATE
Remote Directory : /archive/dispatch
Filename         : dispatch_{{Promoted.DispatchDocument.DocumentNo}}.xml
Carrier SFTPoutbound dir fetch Scheduler + SFTP Caller every 15 min, two-way BUS SFTP Caller, broker atomic write SFTP Caller, archive /archive/dispatch/ Broker

A naive script doing the same job, download, upload, delete, works until the broker's server is down at 03:00, and then nobody can say which files made it across. Here the archive subscription is the flight recorder: every document that entered the relay is on disk under its document number, whatever happened on the delivery leg, and a dispute is settled by replaying the archived run, design for replay.

When it fails. Three ports, one shared exception type, RelayFailed, and one O365 Mail operations subscription; tracking shows which port raised it. The legs are independent, a broker outage suspends deliveries while fetching and archiving continue, and the backlog drains through retries or replay when the server returns. Treat an archive failure as seriously as a delivery failure: an audit trail with holes is the kind that fails its first real test.

EXPRESSION
{{Message.MessageType}} == "RelayFailed"
Don't re-fetch what you already relayed. The carrier's directory is the source of "what's new", track processed filenames (move them aside on the server if the carrier allows, or keep a processed-list the fetch checks) so an overlapping poll doesn't relay the same document twice. If a duplicate does slip through, the broker delivery should be idempotent on the document number, which is also the outbound filename.

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 one Application. Under Applications, create AcmeLogistics with Namespace AcmeLogistics and select it, so every port and message type below is 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
DispatchDocumentXMLthe carrier’s document, relayed body-untouched
RelayFailedXMLthe exception type the failure path publishes under (Step 7)

Promotions live on the message type, so add them while you are here. Both delivery filenames (Steps 5 and 6) and the alert subject (Step 7) bind these tokens, and adapter parameters are plain strings: they take promotions, never body paths.

Promotion on DispatchDocument
PromotionPath
DocumentNo/DispatchAdvice/DocumentNo
Promotion on RelayFailed

A failed relay re-publishes the document payload intact, but promotions are type-qualified, so the exception type needs its own:

PromotionPath
DocumentNo/DispatchAdvice/DocumentNo

2
Step Two
Create the Authentications

Four remote systems, four 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.

Carrier server, for the fetch port
SettingValue
NameCarrierSftp
AdapterSFTP Caller
DefinitionSFTP Password Authentication
Username / Passwordthe carrier’s SFTP login
Host Key Fingerprintthe carrier server’s fingerprint
Broker server, for the delivery port
SettingValue
NameBrokerSftp
AdapterSFTP Caller
DefinitionSFTP Password Authentication
Username / Passwordthe broker’s SFTP login
Host Key Fingerprintthe broker server’s fingerprint
Archive server, for the archive port
SettingValue
NameArchiveSftp
AdapterSFTP Caller
DefinitionSFTP Password Authentication
Username / Passwordyour archive server’s SFTP login
Host Key Fingerprintthe archive server’s fingerprint
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 Scheduler receive port
General

Create the Scheduler receive port RelayClock; its tick is what wakes the fetch port (Step 4).

SettingValue
AdapterScheduler
Repeat Every15
Repeat UnitMinutes

4
Step Four
Create the fetch port
General

Create a two-way SFTP Caller send port CarrierFetch with the download command against the carrier’s outbound directory. Subscription: {{Config.PortName}} == "RelayClock". (The dropdowns can also create types and maps inline; building the dependencies first keeps each dialog a selection.)

SettingValue
AdapterSFTP Caller
WayTwo
Hostsftp.carrier.example
CommandDownload
Remote Directory/outbound/dispatch
Pair the credential

Set Authentication to CarrierSftp, from Step 2.

Response side

The fetched file rides back as the response and re-enters the bus through the port’s Response stages: set Message Type Fallback to DispatchDocument, from Step 1, body untouched. Nothing tracks processed filenames for you; keep duplicates out the manual way, move fetched files aside on the carrier server if they allow it, or keep a processed-list the fetch checks (the note above).

A sample dispatch document as the carrier publishes it:

XML
<DispatchAdvice>
  <DocumentNo>CMR-55812</DocumentNo>
  <Carrier>NordFreight</Carrier>
  <Consignee>Beltrans Customs BV</Consignee>
  <Packages>14</Packages>
</DispatchAdvice>

5
Step Five
Create the broker delivery port
General

Create an SFTP Caller send port BrokerDelivery subscribing on {{Message.MessageType}} == "DispatchDocument".

SettingValue
AdapterSFTP Caller
WayOne
Hostthe broker’s server
Remote Directorythe broker’s inbound directory
Overwrite PolicyFail if exists
Pair the credential

Set Authentication to BrokerSftp, from Step 2.

Set the filename

The Filename is rebuilt from the Step 1 DocumentNo Promotion, so the broker copy and the archive copy (Step 6) always match, and a replayed document overwrites nothing at the broker (Fail if exists surfaces the duplicate instead):

EXPRESSION
BrokerDelivery : dispatch_{{Promoted.DispatchDocument.DocumentNo}}.xml
Archive        : dispatch_{{Promoted.DispatchDocument.DocumentNo}}.xml

6
Step Six
Create the archive port
General

Create a second SFTP Caller send port Archive on the same subscription, {{Message.MessageType}} == "DispatchDocument", with the same payload-derived Filename template as Step 5.

SettingValue
AdapterSFTP Caller
WayOne
Hostyour archive server
Remote Directory/archive/dispatch
Create Missing DirectoriesOn
Pair the credential

Set Authentication to ArchiveSftp, from Step 2.


7
Step Seven
Wire the failure path
Set the Exception Message Type

On all three send ports, CarrierFetch, BrokerDelivery and Archive, set Exception Message Type to RelayFailed, from Step 1; the promotion you defined on it carries the document number into the alert subject.

Create the OpsAlert send port

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

SettingValue
AuthenticationO365Ops, from Step 2
Fromnoreply@acme.example
Toops@acme.example
SubjectRelay failed: {{Promoted.RelayFailed.DocumentNo}}

8
Step Eight
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, BrokerDelivery, Archive, OpsAlert and CarrierFetch, then the receive port RelayClock.

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.

Drop a test document

Drop a test document on the carrier server, and confirm it appears on the broker server and under the archive path.

Break it on purpose

Stop the broker server, drop another document: the archive copy still lands, the delivery suspends and alerts, and replay drains it later. That asymmetry, flight recorder intact while a leg is down, is the test that matters.

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.