Skip to main content

Fax: delivery & inbound-routing model

The Fax channel page documents the request shape and limits, and the inbound fax workflow guide walks the receive-path setup. This page is the concept layer underneath both: how the end-to-end fax lifecycle is structured, where it deliberately diverges from the SMS/MMS model, and why. Read it before you wire fax into a reconciliation flow or branch webhook handlers on delivery status.

1. Termination exception: fax rides Telnyx

The platform’s termination policy says outbound voice and SMS exit exclusively through the Devotel softswitch; Telnyx and DIDWW are used for inbound traffic only. Fax is a named exception to that rule — alongside MMS, it is one of the two channels permitted to terminate outbound on Telnyx’s Programmable Fax endpoint. The reason is protocol. A fax is not a message the softswitch’s SMS/e-mail switches can carry: it requires a live T.38 (or G.711 fallback) session negotiated endpoint to endpoint between two fax machines. That session looks exactly like a voice call to result, and Telnyx’s dedicated fax product supports it natively. Forcing fax through the softswitch would mean re-implementing T.38 relay; routing it through the SMS/MMS planes is a category error — those planes are store-and-forward, and T.38 is not. So fax joins MMS as the allowed exception, and your outbound POST /api/v1/messages/fax call terminates on Telnyx by design. Inbound fax rides Telnyx inbound rather than the softswitch — the carrier delivers a fax.received webhook carrying the received PDF as a hosted media URL, and Orbit writes it to the channel-agnostic inbound pipeline. Nothing in this path deviates from your SMS security posture; the exception is narrowly scoped to which provider terminates the fax leg.

2. Document model: PDF/TIFF, quality, and the media handoff

A fax is a document, not a text body. The media_url you supply at send time points the carrier at your PDF or TIFF, and the carrier converts it to T.38 packets in real time. Orbit never re-hosts the file — the carrier fetches directly from your URL at dispatch. Three facts define the document model:
  • PDF or TIFF, HTTPS only. The single permitted file types are PDF and TIFF, and the URL scheme is hard-bounded to HTTPS. Any other scheme is rejected before the carrier ever sees it.
  • quality is a resolution setting, not an SLA toggle. normal (default), high, and very_high control the scanning resolution of the transmitted pages. Higher values raise the resolution of fine graphics but also lengthen the T.38 handshake, which on a lossy line is when poor_line_quality failures appear. Pick high for documents with small text or signatures; fall back to normal on long documents to lossy destinations.
  • The fax lifecycle is a single attempt. Unlike a data channel, fax has no built-in re-transmission schedule at the carrier: the attempt either succeeds or fails in one go. Orbit’s queue worker re-dispatches the send only while the message is still queued (a transient provider error before the T.38 session begins); once sending begins, the attempt is live and its outcome lands as a delivery receipt.
A governed idempotency key (orbit-fax:<message-id>) covers all of this: a retry of the same POST /messages/fax dedups at the carrier and does not bill twice.

3. Charge semantics: on-success-charge, success vs attempt

Fax takes a stricter stance on charging than the SMS/MMS model: your wallet is billed only when the carrier confirms a successful transmission (delivered or at minimum sent for the non-terminal success state), and failed attempts (busy, no_answer, poor_line_quality, and every other failure diagnostic) are not billed. Success means the carrier completed the T.38 session and acknowledged the transmission — the distinction between sent (carrier-completed) and delivered (the end station confirmed receipt) matters for your reconciliation, but both are charge-eligible. Attempt means the row moved through sending but the handshake never completed — every one of the failure diagnostics documented on the Fax channel page is a non-chargeable attempt. This matters for how you reconcile your wallet ledger: a failed fax row costs zero, and the message.failed events in your webhook stream carry no wallet deduction. Inbound faxes are likewise never billed. The on-success-charge semantics differ from the SMS/MMS per-submit or per-DLR model — see Wallets, credits, and charges for the generic lifecycle and this section for the fax exception.

4. Inbound fax: config endpoints, delivery service, and the routing split

An inbound fax arrives as a Telnyx fax.received webhook on the shared inbound endpoint. The webhook handler validates the event, resolves the tenant from the recipient DID, and writes a messages row with channel: "fax", direction: "inbound". From there, routing decides where the fax goes — the model deliberately separates config from delivery:
  • The routing config endpoints (GET /api/v1/numbers/:id/fax-routing, PUT /api/v1/numbers/:id/fax-routing) read and upsert a per-DID config: { enabled, forward_emails[], deliver_to_inbox, archive_path }. The config lives in the canonical number inventory record and is keyed on the E.164 of the DID, so it survives number reassignment within your account.
  • The delivery service runs after the message row is persisted, and only when the config’s enabled flag is set. It fans the received PDF out to the configured destinations: email forwards (each recipient gets the PDF attached, or a link-only fallback when the media fetch fails) and an inbox ticket (with sender, recipient, and media metadata attached). This service never throws — once the webhook has acknowledged the carrier and the message row exists, a downstream delivery failure degrades to a logged warning rather than a 500.
The critical difference from SMS inbound is conceptual. SMS inbound resolves through the generic routing-rules engine (keyword auto-reply, contact resolution, channel-handoff matrix). Fax has no such engine — a received fax is a document, not an interactive message, and subscribers overwhelmingly want one thing: forward it to a mailbox, open an inbox ticket, or both. The dedicated config/service pair gives exactly that without forcing fax through the text-routing machinery. The general inbound model is documented in Inbound message routing; treat this page as the fax exception.

5. Delivery receipts: the status vocabulary and wire expectations

Outbound fax receipts don’t follow the SMPP DLR vocabulary. Telnyx fax DLRs arrive in the fax.* event namespace, and Orbit maps each event onto the same message status field you use for every other channel: Three expectations make receipts tractable:
  • The delivered vs sent split reflects carrier truth. Some routes promote to delivered immediately after transmission; others only after the receiving machine completes the handshake’s final page. Design your reconciliation to treat sent as carrier-completed-but-non-terminal and delivered as the strong “the customer has it” signal, because sent rows can still be promoted or demoted.
  • Carriers map failure diagnostics onto error_code / error_message. Unknown carrier text passes through verbatim, so branch on the payload fields, not the enum of expected values.
  • Read back through your org-level webhook sink or per-send status_callback. The provider-side external_id is attached asynchronously once the transmission is dispatched, and GET /api/v1/fax/:id/status returns the provider status with a short per-tenant cache — but the durable reconciliation path is the status callback, because the direct-provider readback is a convenience surface, not the record of truth.
Once the vocabulary is clear, the merge contract in Message status transition rules tells you which of these moves your own state store must permit, and the delivery lifecycle page explains who drives each transition.

Where to go next