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.
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:
{{Message.MessageType}} == "DispatchDocument"
Remote Directory : /archive/dispatch
Filename : dispatch_{{Promoted.DispatchDocument.DocumentNo}}.xml
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.
{{Message.MessageType}} == "RelayFailed"
Build it, step by step. The steps run in dependency order: every object is created before the object that selects it.
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:
| Name | Format | Purpose |
|---|---|---|
| DispatchDocument | XML | the carrier’s document, relayed body-untouched |
| RelayFailed | XML | the 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 | Path |
|---|---|
| DocumentNo | /DispatchAdvice/DocumentNo |
A failed relay re-publishes the document payload intact, but promotions are type-qualified, so the exception type needs its own:
| Promotion | Path |
|---|---|
| DocumentNo | /DispatchAdvice/DocumentNo |
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.
| Setting | Value |
|---|---|
| Name | CarrierSftp |
| Adapter | SFTP Caller |
| Definition | SFTP Password Authentication |
| Username / Password | the carrier’s SFTP login |
| Host Key Fingerprint | the carrier server’s fingerprint |
| Setting | Value |
|---|---|
| Name | BrokerSftp |
| Adapter | SFTP Caller |
| Definition | SFTP Password Authentication |
| Username / Password | the broker’s SFTP login |
| Host Key Fingerprint | the broker server’s fingerprint |
| Setting | Value |
|---|---|
| Name | ArchiveSftp |
| Adapter | SFTP Caller |
| Definition | SFTP Password Authentication |
| Username / Password | your archive server’s SFTP login |
| Host Key Fingerprint | the archive server’s fingerprint |
| Setting | Value |
|---|---|
| Name | O365Ops |
| Adapter | O365 Mail Sender |
| Definition | Microsoft Graph |
| Tenant Id / Client Id / Client Secret (Graph AuthConfig) | an app registration with Mail.Send granted |
Create the Scheduler receive port RelayClock; its tick is what wakes the fetch port (Step 4).
| Setting | Value |
|---|---|
| Adapter | Scheduler |
| Repeat Every | 15 |
| Repeat Unit | Minutes |
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.)
| Setting | Value |
|---|---|
| Adapter | SFTP Caller |
| Way | Two |
| Host | sftp.carrier.example |
| Command | Download |
| Remote Directory | /outbound/dispatch |
Set Authentication to CarrierSftp, from Step 2.
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:
<DispatchAdvice> <DocumentNo>CMR-55812</DocumentNo> <Carrier>NordFreight</Carrier> <Consignee>Beltrans Customs BV</Consignee> <Packages>14</Packages> </DispatchAdvice>
Create an SFTP Caller send port BrokerDelivery subscribing on {{Message.MessageType}} == "DispatchDocument".
| Setting | Value |
|---|---|
| Adapter | SFTP Caller |
| Way | One |
| Host | the broker’s server |
| Remote Directory | the broker’s inbound directory |
| Overwrite Policy | Fail if exists |
Set Authentication to BrokerSftp, from Step 2.
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):
BrokerDelivery : dispatch_{{Promoted.DispatchDocument.DocumentNo}}.xml Archive : dispatch_{{Promoted.DispatchDocument.DocumentNo}}.xml
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.
| Setting | Value |
|---|---|
| Adapter | SFTP Caller |
| Way | One |
| Host | your archive server |
| Remote Directory | /archive/dispatch |
| Create Missing Directories | On |
Set Authentication to ArchiveSftp, from Step 2.
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 O365 Mail port subscribing on {{Message.MessageType}} == "RelayFailed":
| Setting | Value |
|---|---|
| Authentication | O365Ops, from Step 2 |
| From | noreply@acme.example |
| To | ops@acme.example |
| Subject | Relay failed: {{Promoted.RelayFailed.DocumentNo}} |
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.
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 on the carrier server, and confirm it appears on the broker server and under the archive path.
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.