Message Level Status - phax/phoss-ap GitHub Wiki

Message Level Status (MLS)

Overview

MLS is a Peppol-defined asynchronous feedback mechanism that allows the receiving Access Point (C3) to inform the sending Access Point (C2) about the processing outcome of a received business document. MLS messages are themselves regular Peppol documents sent via AS4, reusing the existing outbound transaction infrastructure including retry logic.

The AP handles MLS in both roles:

  • As C3 (receiving): After determining the forwarding outcome, the AP generates and sends an MLS response back to C2.
  • As C2 (sending): The AP receives MLS responses asynchronously via AS4, correlates them with outbound transactions, and updates the MLS status.

MLS Response Codes

Code Name When sent
AP Approved Successful delivery with confirmation (forwarding mode http_post_sync or http_post_async)
AB Accepted Blind Delivered without delivery confirmation (forwarding mode sftp, s3_link, or filesystem)
RE Rejection Document failed verification (status reason SV, BV and/or BW — see verifier-driven issue mapping) or permanent forwarding failure to C4 (status reason FD)

The choice between AP and AB depends on the forwarding mode: HTTP modes provide delivery confirmation, while SFTP, S3, and Filesystem do not.

MLS Status Reason Codes

Included in MLS rejection (RE) response line items.

Code Name Description
BV Business Rule Violation (Fatal) Fatal business rule violation -- document does not conform to required business rules
BW Business Rule Warning Non-fatal issue, may accompany a BV but cannot trigger rejection alone
FD Failure of Delivery Permanent inability to forward the document to C4 (error field is NA)
SV Syntax Violation XML schema or syntax validation failure

MLS Sending (AP as C3)

When the AP receives an inbound business document and determines the forwarding outcome, it generates an MLS response and sends it back to C2.

Triggers for MLS creation

Trigger MLS response code Status reason
Successful forwarding via HTTP AP (Approved) --
Successful forwarding via SFTP, S3, or Filesystem AB (Accepted Blind) --
Document verification failure RE (Rejection) SV, BV and/or BW per issue — chosen by the verifier (see below)
Permanent forwarding failure (max retries exhausted) RE (Rejection) FD (Failure of Delivery)

Since v0.12.0 a transaction with verification_result = rejected gets exactly one MLS, the RE of the rejection. Such a transaction only exists if a rejected document is nevertheless forwarded to C4 via verification.inbound.rejection-forwarding — see Forwarding a rejected document. Any later MLS for it is suppressed, because it would contradict the RE that was already sent: neither the positive AP / AB after a successful forwarding, nor the "forwarding failed" MLS when the retries are exhausted, is sent. The suppression is derived from the persisted verdict, so it applies identically to the initial receive path, the retry scheduler and the operations endpoints, and mls_response_code stays RE.

Who triggers the positive MLS (since v0.13.0)

The table above equates "the forwarding HTTP call returned 2xx" with "the document was delivered". For a C3 that forwards directly to C4 that is true. For a C3 that forwards to a middleware in front of C4 it is not: the middleware accepted the document, but the delivery to C4 is still pending. The AP sent at that moment states a fact that has not happened yet, and a later failure between middleware and C4 can no longer be reported, because the MLS for that message is already closed.

The mls.sending.trigger property decides who makes that statement:

Value Behaviour
auto (default) The positive MLS is sent immediately after the successful forwarding. Byte for byte the behaviour of all versions before v0.13.0.
api The positive MLS is deferred. The Receiver Backend reports the real outcome via POST /api/mls/send, and only then is the MLS built and sent.

Only the success path is deferred. Everything that happens before the document reaches the backend stays automatic, because the backend cannot report on a document it never received:

  • the RE of a failed inbound verification, and
  • the AB after exhausted forwarding retries.

Blocking the forwarding call until C4 confirms is not an alternative to this mode: the forwarding runs synchronously inside the AS4 request, so while the HTTP response is pending no AS4 Receipt is returned to C2 and C2's response timeout expires, which makes it retransmit the message.

The watchdog

A backend that stays silent must not push the AP out of MLS-1 compliance — 99.5 % of all MLS responses within 20 minutes, measured from the reception of the business document. In the trigger mode api the retry scheduler therefore runs a fourth cycle: every transaction in status forwarded without an mls_response_code, whose as4_timestamp is older than mls.sending.api.timeout (default 5m), is answered with the fallback MLS mls.sending.api.timeout.code (default AB).

The window is measured from the reception of the document and not from its forwarding, because that is where the MLS-1 clock starts. Anchoring on the forwarding would add the forwarding duration on top of the SLA budget instead of consuming it from it — a document that only reached C4 after 15 minutes of forwarding retries would then be answered at minute 20 at the earliest. A slow forwarding therefore shortens the window of the backend, and in the extreme case the fallback goes out with the first cycle after the forwarding, which is the correct answer when there is no budget left.

Excluded from the watchdog, exactly as from the endpoint, are inbound MLS and MLR documents — they are never answered with an MLS — and documents with verification_result = rejected, which already got the RE of their rejection. Excluded as well is everything with the MLS type FAILURE_ONLY: such a transaction never gets a positive MLS at all, so recording a fallback response code for it would put nothing on the wire and only block the rejection the backend may still report. No database migration is needed for this; the existing columns carry all the information.

The check is part of the existing retry scheduler cycle, so it needs no interval of its own and the effective delay is up to one retry.scheduler.interval longer than the configured timeout. It emits the scheduler span phoss.ap.scheduler.cycle with phoss.ap.scheduler.name = mls-fallback.

Interaction with the MLS type

mls.sending.trigger MLS type (SBDH) Successful forwarding API call Result
auto ALWAYS_SEND yes -- AP / AB immediately
auto FAILURE_ONLY yes -- no MLS
api ALWAYS_SEND yes AP AP when the backend reports
api ALWAYS_SEND yes none within the timeout fallback AB
api ALWAYS_SEND yes RE + issues RE with issues
api FAILURE_ONLY yes AP / AB recorded only, not sent
api FAILURE_ONLY yes RE + issues RE sent, whenever the backend reports it
api FAILURE_ONLY yes none within the timeout nothing — the watchdog skips FAILURE_ONLY, so a later RE is still possible
api any permanently failed -- automatic AB, as always

POST /api/mls/send is not limited to the trigger mode api. In auto mode it works too, as long as no MLS was determined for the transaction yet — which is what lets a backend report an RE for a document it received but cannot process.

Exactly one MLS per business document (since v0.13.0)

Five parties can want to answer the same inbound transaction: the receive path after a successful forwarding, the permanent failure of the forwarding retries, the verification rejection, the MLS watchdog and the Receiver Backend through POST /api/mls/send. Since v0.13.0 they all create their MLS through the same method, and that method claims the MLS slot of the transaction atomically before it builds anything: UPDATE ... SET mls_response_code=? WHERE id=? AND mls_response_code IS NULL. The first writer wins, every later one is told that the MLS is already decided, and nothing is built or sent for it.

The claim is what a check of the current response code could never be. The transaction the receive path works with is a snapshot that was read before the forwarding call, so a backend that reported an outcome in the meantime is simply not visible in it. The concrete case: the middleware receives and processes the document but the HTTP response is lost, the AP goes to forward_failed and schedules a retry, the backend — which does have the document — reports the outcome through the endpoint, and the retries then exhaust. Without the claim the exhausted retries would send a second, contradicting MLS.

If the creation fails after the claim was won, the claim is given back, so a later attempt can still answer C2. A response code that already belongs to a real MLS outbound transaction can never be erased by that.

MLS sending strategy (MLS_TYPE)

For inbound documents, the MLS_TYPE of the received SBDH controls when MLS responses are sent. If it is missing or invalid, FAILURE_ONLY applies. The mls.type configuration property is not used for this - it only sets the MLS_TYPE of outbound documents.

Value Behavior
ALWAYS_SEND MLS response is sent for all outcomes (RE, AP, or AB)
FAILURE_ONLY (default) MLS response is sent only on rejection (RE)

The active strategy is captured per inbound transaction at reception time.

MLS sending (as C3) can be globally disabled via mls.sending.enabled=false -- see Configuration.

MLS_TO routing

By default, the MLS response is sent to C2's Peppol Seat ID. The Sender Backend can override this per outbound transaction by providing the mlsTo query parameter on the submission API, which places an alternative Peppol participant ID in the SBDH extension. This is useful when C2 operates multiple access points.

The mlsTo value can be set on:

  • POST /api/outbound/submit/{senderID}/{receiverID}/{docTypeID}/{processID}/{c1CountryCode} -- via the mlsTo query parameter (see API Specification)
  • POST /api/outbound/submit-auto/{senderID}/{receiverID}/{c1CountryCode} -- via the mlsTo query parameter (see API Specification)
  • POST /api/outbound/submit-s3 -- via the mlsTo field in the JSON body (see API Specification)

Determining the effective MLS receiver (since v0.11.0)

When acting as C3, the AP determines where to send the MLS response following the Peppol MLS SPOG section 5.1 fallback chain:

  1. Use the MLS_TO value from the incoming SBDH only if it is present, syntactically a valid 0242 SPIS participant ID, and its 6-digit Main ID correlates to the sending C2's SPID Main ID (derived from the C2 signing certificate's CN). Redirecting an MLS to a different Service Provider is not allowed, so a non-correlating MLS_TO is treated as invalid and ignored.
  2. Otherwise fall back to the default SPID receiver 0242:<C2 Main ID>, derived from the C2 Seat ID.

The validation happens at reception time in InboundOrchestrator.getValidMlsTo(...): a valid, correlating MLS_TO is persisted on the inbound transaction (mls_to), while an absent, syntactically invalid, or non-correlating value is dropped to NULL (with a warning logged) so the default SPID is used.

SMP lookup fallback (since v0.11.0)

Before the MLS is sent, the effective MLS receiver is resolved via an SMP lookup (OutboundOrchestrator). Per MLS SPOG section 5.4, an unreachable receiver is handled differently for MLS than for regular business documents:

  • Custom MLS_TO not reachable — if the effective receiver is a custom MLS_TO (i.e. it differs from the default SPID) and it cannot be resolved in SMP (participant or MLS service not registered), the AP fires the onSpecialMlsToNotReachable() notification and retries the lookup against the default SPID receiver. Because a valid MLS_TO always shares C2's Main ID, the default SPID is 0242:<Main ID> and is derivable from the receiver itself.
  • Default SPID not reachable — if the default SPID receiver itself cannot be resolved, the MLS transaction is queued and retried (per PNP Rule MLS-4) instead of being marked as permanently failed. Retries (which go through the generic two-argument sending path) reconstruct the same fallback automatically for mls_response transactions, so the behaviour is identical to the first attempt.

In contrast, for a regular business_document transaction an unresolvable receiver remains a permanent failure. Transient SMP errors (retry-feasible errors, circuit-breaker rejection) always queue a retry against the same receiver, independent of MLS.

Sending flow

sequenceDiagram
    participant AP as AP (C3)
    participant DB as DB
    participant P4 as phase4
    participant RA as Remote AP (C2)
    participant C4 as MLS copy sink

    AP->>AP: Determine MLS response code (RE, AP, or AB)
    AP->>AP: Check MLS_TYPE (ALWAYS_SEND or FAILURE_ONLY)
    note over AP: If FAILURE_ONLY and outcome is success: skip MLS

    AP->>DB: Create outbound_transaction (transaction_type = mls_response)
    AP->>DB: Update inbound_transaction (mls_response_code, mls_outbound_transaction_id)

    opt forwarding.mls-copy.enabled
        AP-->>C4: Fire-and-forget copy of the MLS (kind = outbound-mls-copy)
    end

    AP->>P4: Send MLS via AS4
    P4->>RA: AS4 UserMessage (MLS)
    RA-->>P4: AS4 Receipt
    P4-->>AP: AS4 result

    AP->>DB: Insert outbound_sending_attempt
    AP->>DB: Update outbound_transaction status
Loading

The MLS sending is recorded as an outbound_transaction with transaction_type = mls_response, linked to the original inbound transaction via mls_inbound_transaction_id. The inbound transaction's mls_outbound_transaction_id is set to reference it back.

MLS response transactions follow the same retry logic as business document transactions -- see Retry and Resilience Patterns.

Forwarding a copy of the outgoing MLS (since v0.12.0)

An MLS received from another AP is forwarded to C4 like any other inbound document. The MLS that this AP generates itself used to stay inside the AP -- so a downstream consumer that needs the validation detail, e.g. to build a Tax Data Summary, never saw it.

Setting forwarding.mls-copy.enabled=true hands a copy of every AP, AB and RE to a separately configurable sink; see MLS Copy Forwarding. Three properties of the dispatch are worth knowing:

  • It happens once the outbound transaction has been created, before the AS4 transmission. The copy is therefore the MLS the AP generated, not proof that C2 received it -- it goes out even if the transmission to C2 subsequently fails.
  • It is fire-and-forget: a failing sink never affects the MLS sending to C2, never touches the inbound transaction status and is never retried.
  • The payload is the bare ApplicationResponse as it is stored, not an SBD. A received MLS arrives as a full SBD, so a consumer that handles both distinguishes them by the kind of the forwardable document (inbound-mls versus outbound-mls-copy).

Verifier-driven issue mapping (since v0.10.0)

Breaking change in v0.10.0. The SPI signature changed: IInboundDocumentVerifierSPI.verifyInboundDocument now returns @Nullable MlsOutcome instead of ESuccess. A null return (or any non-REJECTION outcome) means the verifier passes; a non-null MlsOutcome with response code REJECTION causes rejection and the contained MlsOutcomeIssue entries flow into the MLS line responses. See News and noteworthy for the migration note.

Breaking change in v0.12.0. verifyInboundDocument returns VerificationOutcome instead of @Nullable MlsOutcome. VerificationOutcome.rejected (aMlsOutcome) keeps the issue mapping described here unchanged; VerificationOutcome.serviceUnavailable (sMessage) states that the document could not be inspected at all, which is handled per verification.verifier-fail-mode and is never an implicit rejection. For a rejection without MLS details — and for an unavailability that ends up as a rejection — the AP generates a single FD line response naming the verifier.

Rejection MLS responses now contain a line response per failed validation entry instead of a single generic BV/NA placeholder. Issues are grouped by errorField into MLS line responses (one line response per distinct error field).

Since v0.12.0, line responses are not limited to rejections. MLS permits details on the positive response codes as well, so the warnings of a verification that accepted the document are sent to C2 as line responses of the AP / AB response — previously they were discarded. Whether the MLS is a rejection is decided by the AP's verdict and by the severity of the findings, not by their mere presence:

Verification result MLS response code Line responses
Passed, no findings AP / AB none
Passed, warnings only AP / AB one BW per warning
Rejected, at least one error RE one per finding (SV / BV / BW)
Rejected, warnings only (contradictory) RE a synthesized BV/NA naming the verifier, plus the warnings

AP versus AB is decided by the forwarder, not by the verification: AP for delivery with confirmation, AB without.

Verifier implementations report VerificationIssues; the status reason of each MLS line response is derived from them when the MLS is built. The bundled PhormDocumentVerifier in phoss-ap-validation maps phive ValidationResultLists to issues via PhiveToVerificationMapper, and MlsOutcomeIssue.fromVerificationIssue (...) projects them onto MLS:

First, phive is mapped to a VerificationIssue (since v0.12.0):

phive source phive severity type level
XSD validation artefact (EValidationBaseType.XSD) ERROR / FATAL_ERROR syntax error
XSD validation artefact WARN syntax warning
Schematron / other artefact ERROR / FATAL_ERROR business_rule error
Schematron / other artefact WARN business_rule warning
any below WARN dropped dropped

Then the issue is projected onto MLS. The projection is severity-first, because reporting a warning as a fatal violation would tell C2 that the document is unusable:

level type MLS status reason
error syntax SV (Syntax Violation)
error business_rule BV (Business Rule Violation, fatal)
warning any BW (Business Rule Warning)

Changed in v0.12.0. An XSD entry with severity WARN previously became SV, i.e. a fatal syntax violation. It is now reported as BW. Fatal entries are unaffected, so the MLS produced for a genuinely invalid document is unchanged.

The remaining MLS fields are derived as before:

MLS field Derived from
errorField VerificationIssue.getLocation() — the failed Schematron assertion's XPath context if present, else the formatted IError.getErrorLocation(); NA (CPeppolMLS.LINE_ID_NOT_AVAILABLE) when the issue has no location
description [code] + VerificationIssue.getDescription() when a rule ID is present, otherwise the description alone; falls back to "Validation failure"

The rule ID is a separate field (code) on the neutral issue and is only folded into the description here, because MLS has nowhere else to put it. Consumers of the outbound submit API get it as its own JSON field — see API Specification.

If the call to the configured Phorm Validation Service itself fails (network error, malformed response, missing token), the verifier produces a single BV/NA rejection with description "Phorm validation service call failed - see server log for details". FD is reserved for permanent inability to forward to C4 and is not used for verifier-call failures.

When the Phorm URL is not configured (verification.phorm.url empty), the verifier returns null — the inbound document passes the verification step.

MLS Reception (AP as C2)

When the AP sends a business document to a remote C3, it expects an MLS response back. The MLS response arrives asynchronously as a separate AS4 message.

Reception flow

sequenceDiagram
    participant RA as Remote AP (C3)
    participant PS as phase4 Servlet
    participant DB as DB

    RA->>PS: AS4 UserMessage (MLS response)
    PS->>PS: Parse MLS, extract SBDH Instance ID reference
    PS->>DB: Lookup outbound_transaction by SBDH Instance ID
    DB-->>PS: original transaction

    PS->>DB: Update outbound_transaction MLS fields
    PS-->>RA: AS4 Receipt
Loading

Correlation

The AP correlates the incoming MLS response with the original outbound business document transaction using the SBDH Instance Identifier referenced in the MLS message. On successful correlation, the outbound transaction is updated with:

Field Value
mls_status received_ap, received_ab, or received_re (mapped from the MLS response code)
mls_received_dt The AS4 timestamp when the MLS response was received
mls_id The MLS document's own ID (cbc:ID)
mls_inbound_transaction_id FK to the inbound transaction representing the received MLS message

If no matching outbound transaction is found, the AP logs a warning and triggers the onInboundMLSCorrelationError() notification.

MLS reception status

Tracked on outbound business document transactions as mls_status:

Value Description
pending MLS response has not yet been received
received_ap MLS response received with code AP (Approved)
received_ab MLS response received with code AB (Accepted Blind)
received_re MLS response received with code RE (Rejection)
not_applicable MLS is not expected for this transaction (e.g., MLS response transactions themselves)

SLR Compliance (MLS Timing Requirements)

The Peppol Network Policy defines Service Level Requirements (SLRs) for MLS timing, measured monthly for payloads smaller than 10 MB.

MLS milestones

All milestones are based on AS4 MessageInfo/Timestamp values (UTC).

Milestone Definition Where captured in phoss-ap
M1 AS4 Timestamp when the business document transmission is initiated at C2 outbound_sending_attempt.as4_timestamp (outbound) or inbound_transaction.as4_timestamp (inbound)
M2 AS4 Timestamp when the MLS message transmission is initiated at C3 outbound_sending_attempt.as4_timestamp on the MLS response's successful sending attempt
M3 Date/time when the MLS message is received at C2 outbound_transaction.mls_received_dt

SLR thresholds

SLR Constraint Target Measurement
SLR MLS-1 (Latest MLS Sending) M2 - M1 <= 20 minutes 99.5% This AP as C3: time from receiving a business document to successfully sending the MLS response
SLR MLS-2 (Latest MLS Reception) M3 - M1 <= 25 minutes 99.5% This AP as C2: time from successfully sending a business document to receiving the MLS response

Monitoring SLR compliance

The AP provides dedicated API endpoints for monitoring SLR compliance:

  • GET /api/mls/sla/mls1 -- MLS-1 SLA report (this AP as C3, measuring M2 - M1). See API Specification.
  • GET /api/mls/sla/mls2 -- MLS-2 SLA report (this AP as C2, measuring M3 - M1). See API Specification.

Both endpoints return:

Field Description
totalCount Total number of MLS responses measured
withinSlaCount Number within the threshold
compliancePercent Actual compliance percentage
targetPercent Required target (99.5)
thresholdSeconds SLA threshold in seconds (1200 for MLS-1, 1500 for MLS-2)
meetingSla Whether the target is met
entries Individual measurements with sbdhInstanceID, m1, m2OrM3, durationSeconds, withinSla

API Endpoints

All MLS-related API endpoints. Full details in API Specification.

Method Path Description
POST /api/mls/send Trigger the MLS of an inbound transaction from the Receiver Backend (since v0.13.0)
GET /api/mls/missing List inbound transactions that have not yet received an MLS response
GET /api/mls/sla/mls1 MLS-1 SLR compliance report (C3 side: M2 - M1)
GET /api/mls/sla/mls2 MLS-2 SLR compliance report (C2 side: M3 - M1)

MLS-related fields are also returned on existing transaction status endpoints:

Method Path MLS fields
GET /api/outbound/status/{sbdhInstanceID} mlsTo, mlsStatus, mlsReceivedDT, mlsId
GET /api/inbound/status/{sbdhInstanceID} mlsTo, mlsType, mlsResponseCode, mlsOutboundTransactionID

Database Fields

On inbound_transaction (MLS sending as C3)

Field Description
mls_to MLS_TO target participant ID (from SBDH extension)
mls_type MLS type of the received SBDH, FAILURE_ONLY if missing or invalid (FAILURE_ONLY or ALWAYS_SEND)
mls_response_code MLS response code sent or to be sent (RE, AP, AB)
mls_outbound_transaction_id FK to the outbound_transaction representing the MLS sending

On outbound_transaction (MLS reception as C2)

Field Description
mls_to MLS_TO override participant ID (only for business_document transactions)
mls_status MLS reception status: pending, received_ap, received_ab, received_re, not_applicable
mls_received_dt When the MLS response was received
mls_id The MLS message's own ID (cbc:ID)
mls_inbound_transaction_id FK to the inbound_transaction that represents the received MLS message (only for mls_response transactions)

On outbound_sending_attempt (MLS milestone timestamps)

Field Description
as4_timestamp AS4 MessageInfo/Timestamp (UTC). For business_document attempts this is MLS Milestone M1; for mls_response attempts this is MLS Milestone M2.

See Database Design Notes for the full schema.

Configuration

All MLS configuration properties. See Configuration Properties for the full configuration reference.

Property Default Description
mls.sending.enabled true Global switch to enable or disable all outbound MLS sending. When false, no MLS responses are sent for any inbound transaction.
mls.type ALWAYS_SEND MLS sending strategy: FAILURE_ONLY (only on rejection) or ALWAYS_SEND (for all outcomes). Captured per inbound transaction at reception time.
mls.sending.trigger auto What triggers the positive MLS of a successfully forwarded document: auto (immediately after the forwarding) or api (only when the Receiver Backend calls POST /api/mls/send). Since v0.13.0.
mls.sending.api.timeout 5m Only with trigger=api: after this duration without a reported status, the watchdog sends the fallback MLS. Since v0.13.0.
mls.sending.api.timeout.code AB The response code of that fallback MLS — AB or AP. Since v0.13.0.

Notifications and lifecycle events

The AP dispatches MLS-related events to two runtime extension SPIs, so integrations can react the moment an MLS is received instead of polling the MLS query API.

Success side — IAPLifecycleEventSPI:

Event Method When
MLS received & correlated onInboundMLSCorrelated() An inbound MLS response was successfully matched to the outbound business document it refers to

The onInboundMLSCorrelated() callback carries:

Parameter Type Description
sMlsTransactionID String Transaction ID of the inbound MLS
sReferencedSbdhInstanceID String SBDH Instance Identifier of the original outbound document
sCorrelatedOutboundTransactionID String Transaction ID of the correlated outbound business document — use it to look up sender, receiver, document type and process
eMlsResponseCode EPeppolMLSResponseCode Response code carried by the MLS (ACCEPTANCE / ACKNOWLEDGING / REJECTION)
eMlsReceptionStatus EMlsReceptionStatus Resulting reception status persisted on the outbound transaction (RECEIVED_AP / RECEIVED_AB / RECEIVED_RE)
sMlsDocumentID String (nullable) Document ID of the received MLS ApplicationResponse
aMlsReceivedDT OffsetDateTime AS4 reception timestamp of the MLS
aRoundTrip Duration (nullable) Wall-clock time from outbound send completion to MLS reception; powers the MLS-1 / MLS-2 SLA histograms

Failure side — IAPNotificationHandlerSPI:

Event Method When
MLS correlation error onInboundMLSCorrelationError() An inbound MLS response cannot be correlated to any outbound business document (no matching SBDH Instance ID found)
Special MLS_TO not reachable (since v0.11.0) onSpecialMlsToNotReachable() An outbound MLS could not be delivered to a custom MLS_TO receiver because it was not resolvable via SMP, and sending falls back to the default SPID receiver (see SMP lookup fallback)

The onSpecialMlsToNotReachable() callback carries:

Parameter Type Description
sOutboundTransactionID String Transaction ID of the outbound MLS being sent
sReferencedSbdhInstanceID String SBDH Instance Identifier of the original business document the MLS refers to
sAttemptedMlsToParticipantID String The custom MLS_TO receiver participant ID that could not be reached
sFallbackDefaultSpidParticipantID String The default SPID receiver participant ID used as the fallback target

The OpenTelemetry metrics handler additionally increments the phoss.ap.outbound.mls.special_to_not_reachable counter.

⚠️ **GitHub.com Fallback** ⚠️