Skip to content

feat: keep messages pending while offline - WPB-25320 - #5447

Merged
caldrian merged 67 commits into
developfrom
feat/pending-message-foundation-WPB-26307
Nov 4, 2026
Merged

caldrian merged 67 commits into
developfrom
feat/pending-message-foundation-WPB-26307

Conversation

@caldrian

@caldrian caldrian commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor
TaskWPB-25320 [iOS] Implement retry for messages with Wire Drive disabled

Issue

Milestone 0 of the PRD "Pending connection, pending invite, pending message", covering pending messages only. Pending connections and invites are not part of this PR.

Today a message written while offline fails after about 60 seconds and has to be resent by hand. With the new developer flag offlinePendingMessages turned on, text messages stay pending and are sent automatically once the network is back, including after an app restart.

Approach: this extends the existing send path in ClientMessageRequestStrategy and doesn't add a separate outbox.

  • Waits for the network: before each send attempt, the strategy waits until reachability says the backend may be reachable.
  • Doesn't fail while offline: if a send fails because we're offline, or because the background task was cancelled, the message stays pending. It's sent again later with the same nonce, so receivers deduplicate it. Other transient errors are retried 3 times. Permanent errors expire the message as before.
  • Not expired on restart: pending messages are no longer expired after an app restart.
  • Ordering: the existing rule that a message only waits for earlier messages from the last 3 minutes now also covers older pending messages from the self user, so they're still sent in order.
  • New sendAttemptStarted attribute: added to ClientMessage in data model 2.146.0. It's set just before the first send attempt.
  • In-place edit: while sendAttemptStarted is false, editing a pending text message replaces its text and keeps the nonce. Receivers only see the final text, without an "Edited" marker. Once sending has started, the normal edit is used, or an alert is shown if the edit races with the send.
  • Local delete: while sendAttemptStarted is false, a new pending text message can be deleted ("Delete for Me"). It's removed locally only, since nobody else has it.

Edits and deletes of pending messages run on the sync context, which is also where the strategy sets sendAttemptStarted, so they can't race with a send.

Ephemeral messages and assets keep today's behaviour.

Known limitations / follow-ups

  • Not yet: pending messages don't yet stay at the bottom of the conversation when newer messages arrive (PRD M0 note). This will come in a follow-up PR.
  • Editing on a flaky network: if the device looks online but requests fail, sendAttemptStarted is already set, so the message can no longer be edited or deleted in place.
  • Duplicate deliveries: a resent message has the same nonce, so receivers deduplicate it. The iOS notification extension may still show a second banner. We still need to confirm with the Android, Web and MLS owners that their dedup works the same way.

Testing

Turn on Developer settings → offlinePendingMessages and restart the app. Use a second account to receive.

  1. Offline: send a message and wait more than 1 minute. It stays pending. Go back online and it's delivered once, without tapping Retry.
  2. Offline: send several messages more than 3 minutes apart. After reconnecting they arrive in order.
  3. Offline: edit a pending message. After reconnecting the receiver only sees the new text, with no "Edited" marker.
  4. Offline: edit a message that was already delivered, then edit it again. The receiver sees the final text with the "Edited" marker.
  5. Offline: kill and relaunch the app. The message is still pending and is sent after reconnecting.
  6. Offline: send, then lock the phone and reconnect. The message is delivered, either in the background or when the app is opened.
  7. Offline: delete a pending message ("Delete for Me"). The receiver never sees it.
  8. Self-deleting messages, and everything with the flag off, behave as before.

Checklist

  • Title contains a reference JIRA issue number like [WPB-XXX].
  • Description is filled and free of optional paragraphs.
  • Adds/updates automated tests.

UI accessibility checklist

  • Make sure you use the API for UI elements that support large fonts.
  • All colors are taken from WireDesign.ColorTheme or constructed using WireDesign.BaseColorPalette.
  • New UI elements have Accessibility strings for VoiceOver.

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Please make sure new localizations are translated once merged to develop.
We need 100% German translations before releasing.

New strings (English):

  • message.delete_pending_failed.title = "Message Is Being Sent"
  • message.delete_pending_failed.message = "You can delete this message once it has been sent."
  • message.edit_pending_failed.title = "Message Is Being Sent"
  • message.edit_pending_failed.message = "You can edit this message once it has been sent."

@caldrian caldrian changed the title feat: queue offline messages and recover pending sends - WPB-26307 feat: queue offline messages and recover pending sends - WPB-25320 Oct 26, 2026

@acv-w acv-w left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One P2 issue in pending-message editing.

@acv-w acv-w left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The send-history concern is fixed in 5a2db42. I checked attempt persistence with the flag off, the default for legacy records, and the new retry and migration tests.

CI still fails because FlagSection.init(flag:) in DeveloperFlagsView.swift does not handle .offlinePendingMessages. This build error must be fixed before merge.

@datadog-wireapp

datadog-wireapp Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: e2fb5ee | Docs | View more details | Give us feedback!

@caldrian
caldrian added this pull request to stack #5498 November 4, 2026 11:49
@caldrian
caldrian removed this pull request from stack #5498 November 4, 2026 11:51
@caldrian
caldrian enabled auto-merge November 4, 2026 11:51
The 2.118.0 fixture used the current message-composition API, which now writes sendAttemptStarted from schema 2.146.0 and raises an unrecognized-selector exception on the old model. Construct fixture messages directly with source-schema fields and document why this setup must stay independent of current composition requirements.

Validation: targeted migration test passed locally as confirmed by the developer; SwiftFormat, SwiftLint, and git diff --check passed.

@jullianm jullianm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the current revision, including the send-history and expired-request fixes from the previous review. One remaining P2 issue: the new in-place edit/delete safety check does not cover the link-preview send path.

Validation: traced both send paths and the preview preprocessing/selection code at this commit. I did not run the iOS test suite locally. SwiftLint and SwiftFormat pass; PR tests were still pending when checked.

@caldrian
caldrian disabled auto-merge November 4, 2026 15:03
@caldrian
caldrian enabled auto-merge November 4, 2026 15:27
@caldrian
caldrian added this pull request to the merge queue Nov 4, 2026
Merged via the queue into develop with commit b4724c2 Nov 4, 2026
13 checks passed
@caldrian
caldrian deleted the feat/pending-message-foundation-WPB-26307 branch November 4, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Messenger Jira component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants