REFRACT Delivery

REFRACT Delivery

REFRACT Delivery provides secure document dispatch and delivery for REFRACT PDF-generated documents. Send documents to external recipients via time-limited access links, with access auditing and proof of delivery.

REFRACT Delivery handles the final stage of the document lifecycle, getting a rendered PDF securely to an external recipient, with a delivery audit trail.

Enterprise Feature

REFRACT Delivery requires the vaultDelivery feature gate on your licence.

Needs Azure Blob Storage

REFRACT Delivery keeps its records, its tamper-evident audit chain and its documents in your Azure Blob Storage account, and cannot run without it. The installer deploys that account on every install, whatever your storage routes say, so routing your documents to SharePoint does not turn REFRACT Delivery off. If a deployment is missing the account's settings, REFRACT Delivery refuses every request with BLOB_STORAGE_NOT_CONFIGURED, naming the missing settings, and the Dispatcher's health check reports blobStorage as missing. See Storage Providers.

Documents stored in your Azure subscription

Documents are generated and stored in your Azure subscription. When a recipient opens a REFRACT Delivery link, the portal redirects the browser to your own Dispatcher or SharePoint site, which serves the document directly - the portal never receives the bytes.

Invitation and one-time-code emails are sent through a REFRACT shared notification service by default, which receives the recipient's email address and name, the document name, the rendered message body, any custom message text, one-time codes, links, and correlation identifiers. No document content is included. For an invitation sent from a REFRACT Dispatch batch, it also receives the batch's identifier, the record number, and a keyed hash of the recipient's address, and it holds the email's result (sent or failed, with a stage and error code, but no address) until your Processor collects it, or for 30 days if it is never collected.

For customers who want notification metadata to stay in their own tenant, customer-acs mode sends notifications directly from your Azure Communication Services instance instead of the shared service. It does not change how the portal redirects for the document itself.


What REFRACT Delivery Does

Secure Email Delivery

Recipients receive a notification email with a time-limited access link. The document is never attached directly to the email; it is retrieved through the secure portal.

Token-Gated Access

Each delivery session issues a unique, HMAC-signed access token. Access itself expires when the session's own expiry is reached, and is revocable by the initiator at any time.

OTP Verification

Optional one-time passcode challenge before a recipient can access the document, providing an additional layer of identity assurance.

Access Auditing

Session creation and each document access are recorded in the audit trail, providing a record of when a recipient accessed the document.

Configurable Expiry

Access links expire after a configurable window (default 7 days). The document becomes inaccessible via the delivery portal once the session expires.


Delivery Modes

ModeDescription
secure-emailSends a notification email to each recipient with a unique, time-limited access link to the delivery portal. The document is retrieved on demand.

A template can ask each recipient to accept a consent policy before the document opens. Set it in the template's templateSettings.delivery.consent block:

{
  "templateSettings": {
    "delivery": { "mode": "SECURE_LINK", "consent": { "mode": "required" } }
  }
}
  • Default. A template with no consent block has no consent step (none).
  • Policy. Without policyId, the recipient is shown the REFRACT system policy refract-standard-consent-delivery. Consent Policies lists its versions and describes how to publish your own.
  • Delivery mode. The step is shown on the recipient's secure link, so it needs delivery mode SECURE_LINK (secure-email). A template that requires consent with SHAREPOINT or BOTH is refused with consent_requires_portal_link.
  • Which template. A manual send (POST /api/delivery) uses the consent setting of the template the document was rendered with; a document with no render record is sent without a consent step. A render's delivery, a REFRACT Dispatch batch, and a workflow's post-seal delivery use their own template's setting.
  • The signing portal. The portal shows the step when its health check lists the deliveryConsent capability. When it does not, a manual send is refused with 503 consent_layer_unavailable and Retry-After: 30, and nothing is sent. A queued delivery sends nothing and records DELIVERY_SKIPPED, which REFRACT Lifecycle shows as Delivery Not Sent: Consent Step.

What the Recipient Sees

When the recipient opens their link, after the one-time code when requireOtp is set, the portal shows the policy. The recipient ticks each of its statements and accepts it (the policy's button, "Agree and continue" on the system policy), or chooses Decline. Until the policy is accepted, the Dispatcher refuses to record an access or serve the document, with 409 CONSENT_NOT_ACKNOWLEDGED, and the portal shows the document's name and sender, not the document.

The audit chain records the step as DELIVERY_CONSENT_POLICY_PRESENTED and DELIVERY_CONSENT_POLICY_ACKNOWLEDGED, with the policy's id, version, and SHA-256.

Declining

The portal asks once more before a decline: "Decline this document?" and "The sender will be told that you declined, and this link will stop working. You cannot undo this." After the decline:

  • The delivery. Its state becomes declined, recorded as DELIVERY_DECLINED with the policy the recipient declined. The link stops working and shows "You declined this document". Other recipients of the same send are not affected.
  • The sender. A notice, "Delivery declined: document", goes to the person who sent the delivery: the signed-in user of a manual send, or the person whose approval sealed the workflow for a post-seal delivery. A delivery with no sender on record, such as a render's delivery or a batch, sends the notice to the template's contact: consent.contacts.withdrawal when it is an email address, else templateSettings.delivery.contactEmail. With neither, no notice is sent.
  • REFRACT Lifecycle. The recipient's row on the Deliveries tab says Declined, and the timeline shows Delivery Declined. Under it, the notice is listed as queued for sending, or as "The sender was not emailed about the decline" with the reason.

A recipient who has accepted the policy can no longer decline: 409 DELIVERY_NOT_DECLINABLE. When the decline cannot be recorded, it is not made, and the portal is answered 503 DELIVERY_DECLINE_NOT_RECORDED with Retry-After: 5, so the recipient can try again.


Email Notifications

REFRACT Delivery sends recipient invitations and OTP codes through REFRACT Notification - the shared notification layer that also serves REFRACT Workflow. Two modes are supported: vault-shared (default, no setup required) and customer-acs (routes all notification emails through your own Azure Communication Services - REFRACT Platform infrastructure not involved).

One setting for Delivery, Workflow, and eSign

The EMAIL_PROVIDER environment variable applies to REFRACT Delivery, REFRACT Workflow, and REFRACT eSign together - configure it once on both your Dispatcher and Processor. REFRACT eSign emails are composed by the eSign Portal and follow the setting for every envelope your Dispatcher creates while it is set. See REFRACT Notification for the full breakdown.

See the REFRACT Notification reference for architecture diagrams, environment variable configuration, and mode comparison.


Reminders

REFRACT Delivery reminds a recipient who has not downloaded the document. Reminders are on by default for every customer.

One reminder per recipient, no repeats. The reminder is sent beforeExpiryDays (default 2) before the recipient's secure link expires, and only if the recipient has not downloaded the document. The sweep runs hourly.

What the recipient receives. The invitation, worded as a reminder: "Reminder: a secure document is waiting for you: <document>" and "This is a reminder. <org> sent you a secure document that has not been downloaded yet", with the same Download Document button and the recipient's same secure link. It is sent through the same queue and Processor as the invitation, so EMAIL_PROVIDER (customer-acs included) decides how it is sent.

When no reminder is sent. A reminder is not sent:

  • for a link that lives less than beforeExpiryDays plus 2 days;
  • to a recipient who has downloaded the document;
  • to a recipient whose link was revoked or has expired;
  • to a recipient whose secure-link email never went out;
  • to a recipient who was already reminded.

"Downloaded" means the recipient's download was served from the secure link, which is recorded as firstDownloadedAt. Opening the page is not a download.

Settings.

SettingTypeDefaultRangeDescription
enabledbooleantrueSet to false to turn reminders off.
beforeExpiryDaysnumber21 to 14Days before the secure link expires to send the one reminder.

Set them as templateSettings.delivery.reminders, or as a reminders object in the POST /api/delivery body (see the API reference). The narrowest layer wins field by field: the built-in default, then the template's reminders block, then the request's. Values must be whole numbers; anything else is refused with HTTP 400 and a sentence naming the setting (DELIVERY_REMINDERS_INVALID; on a render, before the render runs). A workflow's post-seal delivery uses its delivery block's reminders. The resolved schedule is stored on the session when it is created, so a session created before reminders were introduced has none and is never reminded.

{
  "templateSettings": {
    "delivery": {
      "reminders": { "beforeExpiryDays": 3 }
    }
  }
}

Timing rules, audit trail and kill switch

  • A reminder is not sent within 2 hours of expiry, and waits while the recipient was emailed in the last 12 hours.
  • After downtime only the latest due reminder is sent. There are no quiet hours: a reminder goes out at the first sweep after it is due. Reminders are email only, and do not count as a new session for licensing or metering.
  • The audit chain records DELIVERY_REMINDER_SENT with its outcome (sent or failed). The Lifecycle timeline shows a Reminder Sent sub-entry.
  • REFRACT_REMINDERS_DISABLED=true on the Dispatcher Function App stops every reminder across the deployment.

Each recipient's link is registered with the signing portal before the email goes out, and the email carries the link reference (/d/<linkReference>), not a token. When the portal cannot be reached, times out, or answers with a 5xx, the session is created and its invitation is held: a manual send answers that session with held: true and no portalUrl (see the API reference). The Dispatcher tries to register the link again about 1, 5, 15, 30, and 60 minutes after the hold, then every hour, and sends the invitation once the link registers. It stops trying when a quarter of the link's life is left, or two hours before the link expires, whichever comes sooner. A held reminder works the same way. A held email is dropped when it is no longer needed, for example when the delivery was revoked, declined, or has expired, or the document was already downloaded.

The Lifecycle timeline shows Waiting to Send (or Reminder Waiting to Send) while the email is held, Invitation Queued After a Hold (or Reminder Queued After a Hold) once it goes out, Invitation Not Sent (or Reminder Not Sent) when the link did not register in time, and Held Invitation No Longer Needed when it was dropped.

An older email's link in the token form (/deliver/{deliveryId}?t=...) no longer opens the document. The portal shows Link unavailable: "This link could not be opened. Please re-open the link from your most recent email, and contact the sender if the problem continues."


Delivery Session States

StateDescription
pendingSession created; notification email not yet enqueued.
sentNotification email successfully enqueued (set immediately after comms queue write).
openedRecipient opened the delivery portal or accessed the document.
expiredSession TTL elapsed before the recipient accessed the document.
revokedSession manually revoked by the initiator.
declinedThe recipient declined the consent policy (see Declining). Their link no longer works.

There is no separate downloaded state. A recipient's first download is recorded separately, as firstDownloadedAt, when the download is served from the secure link. Opening the portal page is not a download. The reminder below looks at firstDownloadedAt, not at the session state.


Proof of Delivery

Two events are recorded to the audit trail for each delivery session, and a third when a reminder is sent:

EventDescription
DELIVERY_SESSION_CREATEDSession created; access link issued.
DELIVERY_DOCUMENT_ACCESSEDRecipient accessed the document. Recorded with the session's running view count.
DELIVERY_REMINDER_SENTThe automatic reminder was sent to the recipient. Records the outcome (sent or failed).

Revoking a session updates its state but does not write an audit event.


Batch Dispatch

REFRACT Delivery is used as the delivery layer for REFRACT Dispatch governed batch operations. Batch dispatch is initiated via POST /api/render with a template that has distribution.mode configured. The Dispatcher validates and queues the batch; after approval (or immediately if requireApproval: false), the Processor picks up the queue message and processes each record independently - rendering a PDF per record and issuing a DeliveryJobMessage to the delivery Service Bus queue for each recipient.

Batch Size

A batch holds at most 1,000 records, or fewer where your license sets a lower limit. A larger batch is refused before anything is stored: the response is an error-report PDF with status 400 and code BATCH_TOO_LARGE, stating the limit and the number of records received, and no batch, receipt or Activity item is created and nothing is sent. To send more documents, split the records into several smaller batches and submit each one. Each batch dispatch processes records sequentially within the processor queue message. For large batches (over a few hundred records), the payload is written to Blob Storage (vault-dispatch-payloads container) at intake time and loaded by the Processor from there - the queue message carries only the batchId reference.



Scenarios

Scenario 1 - Invoice Delivery to External Vendor

Business context: After AP approves a purchase order, the signed invoice PDF is
delivered securely to the vendor via email link. The vendor can download it for
7 days; access is logged for audit.

POST /api/delivery
{
  "correlationId": "inv-2026-0601-acme",
  "documentName": "Invoice INV-2026-0601.pdf",
  "pdfBlobPath": "generated-documents/invoice/2026/06/inv-2026-0601-acme.pdf",
  "mode": "secure-email",
  "recipients": [
    { "email": "accounts@acme.com", "name": "ACME Accounts", "role": "vendor" }
  ],
  "requesterOrg": "Contoso Ltd",
  "message": "Please find your invoice attached. This link expires in 7 days.",
  "expiresInHours": 168
}

Flow:
  Session created → notification email queued → vendor receives email with portal link
  ├─ Portal link: opens portal.refractplatform.com UI (REFRACT Shared Services)
  │   → portal redirects the browser to your Dispatcher or SharePoint site, which serves the document directly
  Vendor opens link → DELIVERY_DOCUMENT_ACCESSED event recorded, session state moves to opened
  Audit trail updated: proof of access for the AP record
  Session expires after 7 days → further access refused

Scenario 2 - Confidential HR Document with OTP Verification

Business context: HR sends an employment contract to a new hire. The document
contains personal data; OTP verification adds a second layer of identity assurance
before the recipient can download.

POST /api/delivery
{
  "correlationId": "hr-contract-2026-jsmith",
  "documentName": "Employment Contract - J. Smith.pdf",
  "pdfBlobPath": "generated-documents/hr-contract/2026/06/hr-contract-2026-jsmith.pdf",
  "mode": "secure-email",
  "recipients": [
    { "email": "j.smith@personal.com", "name": "J. Smith", "role": "employee" }
  ],
  "requesterOrg": "Contoso Ltd",
  "expiresInHours": 72,
  "requireOtp": true
}

Flow:
  Session created → invitation email sent
  Recipient opens portal link → document metadata shown (name, expiry, requireOtp flag)
  Recipient requests OTP → 6-digit code sent to j.smith@personal.com
  Recipient enters OTP → identity verified
  Document accessed → DELIVERY_DOCUMENT_ACCESSED event recorded, session state moves to opened
  Personal data (email) stored AES-256-GCM encrypted in your Table Storage

Scenario 3 - Batch Contract Distribution After Approval

Business context: After a REFRACT Dispatch batch is approved and processed by the
Processor, each record in the batch receives its rendered PDF via a delivery session.
This is triggered automatically by the Processor's DeliveryJobMessage queue handler
- no manual API call is required per recipient.

Processor (automatic, triggered from the delivery queue):
  For each record in the batch:
    Generates deliveryId + HMAC-signed access token
    Writes DeliverySessionEntity to Table Storage
    Enqueues CommJobMessage → notification email sent to recipientEmail field

What crosses to REFRACT Shared Services (vault-shared mode):
  Recipient email address - for ACS delivery
  Portal access link - a link reference (/d/<linkReference>), no token
  Document name - shown in email and portal

What stays in your subscription:
  Document bytes - the portal redirects the browser to your Dispatcher or SharePoint site, which serves them directly; the portal never receives them
  All delivery session state and audit events

API Reference

Full reference for creating delivery sessions, querying session state, revoking access, and recording access events.

Dispatcher Deployment and Link Lifecycle

What happens to outstanding delivery links when you redeploy or move your Dispatcher, and how to plan around it.

On this page