OpenE2EE

Delivery, mailboxes, and push

Exact destination delivery, durable pull, acknowledgment, backlog behavior, and best-effort notification profiles.

Relay owns one durable mailbox for each device and mailbox generation. A send is durable only after the destination mailbox stores the encrypted envelope or shared-body reference. Pull and acknowledgment remove stored work through the same device authority.

For a durable direct send (user-visible or background-sync), 202 means stored. Relay answers HTTP 202 only after the destination mailbox commits the encrypted envelope. An ephemeral send stores no ciphertext, and Relay answers 202 only after the online recipient device accepts it.

Delivery units

One delivery unit is one stored encrypted envelope or shared-body reference for one destination device. Each stored Relay delivery receipt is also one delivery unit.

An exact retry with the same stable operation identifier gets the stored answer. It uses no new unit while Relay keeps its retry identity. Relay keeps it while the destination mailbox holds the message. After removal by acknowledgment or expiry, Relay keeps the retry identity of the last 256 messages removed from that mailbox. A later retry is a new delivery and uses a new unit, and the recipient session refuses the replayed ciphertext.

A direct message to one account with two destination devices uses two delivery units. A group send uses approximately one unit for each destination device that stores its shared-body reference. The direct encrypted-envelope limit is 256 KiB.

Mailbox behavior

Each device mailbox has a 3,000-message working ceiling. At the maximum envelope size, that ceiling is 786,432,000 bytes. This is 78.6432% of the Free plan's storage allowance before other ciphertext.

Relay never silently evicts a live message to make space. A full mailbox still permits pull, acknowledgment, expiry, deletion, and revocation. A new direct send receives a typed MAILBOX_FULL refusal, nothing is stored, and the send is not charged.

Relay delivery receipts and E2EE receipts

Two receipts can tell a sender that a message arrived. They mean different things.

  • A Relay delivery receipt is the Relay's record that the recipient device stored and acknowledged a message. It never proves decryption. It sets the delivery status to delivered with source: 'relay'.
  • An E2EE receipt is the end-to-end encrypted receipt that a recipient device sends. It sets the delivery status to delivered with source: 'e2ee' and decryptionConfirmed.

The delivery status only moves forward, and it keeps every source that reported it.

Project policy

relayReceipts is a policy of each project environment, and it is on by default. Change it on the project settings page in the console. When it is on, Relay makes a Relay delivery receipt when the recipient device acknowledges a message. Relay makes none for sealed-sender, self, background-sync, ephemeral, or expired messages.

When relayReceipts is off, Relay makes no Relay delivery receipt. The receipted list is empty, and the SDK sends its own E2EE receipt under the 'auto' rules. A message that a send stored before the change still makes a receipt when the recipient device acknowledges it. The SDK option deliveryReceipts is a separate setting.

Wire frames

Relay stores one receipt for each sender account and acknowledgment batch, at each active device of the sender. A delivery-receipt frame carries at most 32 receipts. Each receiptId has 43 characters.

{"type":"delivery-receipt","receipts":[{"receiptId":"Zm9v…","recipient":{"accountId":"acc_b","deviceId":2},"messageIds":["m1","m2"],"deliveredAt":1791300000000}]}

The sender device removes receipts with acknowledge-receipts. One frame takes 1 to 100 IDs, and Relay sends no reply. A frame with more than 100 IDs closes the socket with code 1008.

{"type":"acknowledge-receipts","receiptIds":["Zm9v…"]}

Relay answers the recipient device's acknowledgment with an acknowledged frame. Its receipted list holds the message IDs for which Relay started a receipt.

{"type":"acknowledged","messageIds":["m1","m2"],"receipted":["m1"]}

Over HTTP, /mailbox/pull returns receipts next to messages. /mailbox/acknowledge takes an optional receiptIds list and returns receipted and receiptsAcknowledged. The mailbox-capabilities frame and the pull field relayReceipts: true state protocol support only, not the project policy.

Retention and limits

A Relay delivery receipt has the same retention as its message, and never more than 30 days. Delivery is best effort. Relay makes one attempt to store each receipt and never retries it. When the sender mailbox is full, Relay drops the receipt. A receipt never makes a send fail with MAILBOX_FULL. Each device mailbox keeps at most 3,000 receipts and removes the oldest one past that limit.

Billing

Each stored Relay delivery receipt is one delivery unit for each sender device that stores it. A dropped receipt uses no unit. A message is one unit for each recipient device.

Receipt units depend on acknowledgment batches and active sender devices. They do not always double the message units. Turn relayReceipts off to stop these Relay receipt units.

An E2EE receipt is an encrypted envelope, so it uses delivery units as a message does. The Console delivery estimate takes a separate total for stored Relay receipt deliveries. Include E2EE receipt envelopes in its message count.

Push and notification profiles

Expo, APNs, FCM, and Web Push carry best-effort wakes. Provider payloads contain no message plaintext, ciphertext, sender or recipient identity, device or message identifier, group or conversation identifier, attachment reference, or receipt state. A wake tells the SDK to authenticate, pull all pending mailbox work, process each envelope, and acknowledge accepted work.

Push providers can lose, delay, duplicate, or reorder a best-effort wake. Delivery correctness therefore depends on mailbox pull, not on a push provider. Relay removes an invalid provider token after the provider returns its terminal invalid-token result.

The device registers one profile that the project allows:

ProfileiOS behaviorSpecial Apple approval
background-onlyDiscretionary background wakeNo
visible-alertGeneric visible alert, then application pullNo
nse-visibleGeneric alert that invokes an eligible Notification Service ExtensionNo
nse-filteringThe extension can modify or suppress the alertYes

nse-filtering stays unavailable until Apple approves the filtering entitlement and a signed physical-device build proves extension execution and suppression. The entitlement does not improve APNs transport or guarantee execution. A Notification Service Extension does not require that entitlement when it only modifies visible content.

Configure iOS

Use the CLI after the first acknowledged encrypted message:

oe notifications setup ios --profile background-only
oe notifications setup ios --profile visible-alert
oe notifications add-nse
oe notifications verify ios

Expo applications need a development or native build. Expo Go cannot receive this remote-push configuration or contain OpenE2EE's extension. Expo CNG uses a generated Apple target. Bare React Native uses the same generated, generic Swift source and an exact Xcode target handoff.

The generated extension has a timeout-safe generic fallback. It does not receive App Group or Keychain access by default and does not decrypt a preview. Use oe notifications verify ios --app-bundle PATH to inspect a signed build. A Simulator build proves target generation and compilation, not physical-device execution, locked-device behavior, or Apple-approved filtering.

On this page