The order number I need might be on another screen. If I'm halfway through a support message, I'd like to go and find it, then come back to what I was writing. I wouldn't expect the draft to disappear because I pressed Back. I'd still be signed in, after all.
Leaving the chat
With DobroDesk, I can hide the view or unmount it and keep the signed session, draft and pending outbox. Hide also releases keyboard focus. I'd still need to type on the screen I'm going to, so the hidden chat has to let go of the keyboard. The customer can carry on with what they came to do.
The draft I want to come back to is mine. If somebody else signs in on the phone, I'd expect it to be gone. I wouldn't want them to open chat and find my message waiting to go out. That's why the host has to clear this state on signout and before it gives the SDK a different customer's identity.
When the response doesn't come back
I could have one message on the server and no message ID on my phone. If I try again, I'd still want it to be the same message. I might only have lost the response to the first attempt. I'd keep the request key from that first attempt. A new one could add another copy of my message.
I might have started with "Where is my order?" and be writing "Thanks, I found it" by the time the retry runs. I wouldn't want it to send the second text in place of the first. DobroDesk saves the first request before sending it, with its body, original timestamp and request key. That's what the outbox gives back to a retry.
So I retry with the saved values. For 24 hours after acceptance, the server can replay the stored response for a retry with the same saved values. A different body with that key gets a conflict. A day later, the message is still part of the conversation. It's the stored response that has expired.
The client unit test fails a send, restores the session in a new client and flushes the same outbox. It compares the two serialized request bodies. They're equal. An item saved for another account isn't flushed. Both clients use the same in-memory store, though.
Returning after the app quits
Keeping a request while a screen is closed tells me very little about what happens when the app quits. I need it to be there after the process has ended. Native integrations provide durable storage for that. If I use the headless client, I have to provide the store myself. I would test quitting and relaunching the app.
I'm coming back to the same conversation as the same customer. The token I had before might have expired by now, so I'd get a valid one before retrying. I'd leave the saved request as it was while renewing that access. The app needs to be active for the retry.
I'd look at the actual conversation, too. Getting the same request back out of storage wouldn't tell me if the server added it twice. I'd let it accept the message and then drop the response. After quitting and reopening the app, I'd retry that saved request within 24 hours. I should find one copy of the message in the conversation.


Top comments (3)
Your point about where acknowledgement happens is the part we had to design for. On an offline-first inspection app I worked on, an inspection started with a temporary local ID; when the device reconnected, the server assigned the real one, and every answer, photo and queue entry was remapped to it. Retries reused the server ID, so a flaky connection couldn't create duplicate inspections. That's the case your integration check describes: press Send just before the signal drops, and the client can't tell a lost request from a lost response. Does the SDK attach a client-generated message ID so the service can drop a resent duplicate?
Yes, although it's a request key in my case. I don't want to rebuild the request from the composer and accidentally give it a new key. The SDK saves the request before sending, and the retry reads it back from the outbox. That's also what it reads after an app restart. The messageId only comes back from the server.
So for your lost response example, the second request would get the first response back. I keep completed responses for 24 hours. I probably should have put this in the article, because just saying pending messages are preserved leaves that question open.
That's a clean split: the key is fixed before the first send, so a retry can't mint a new one. The case I'd test is the 24-hour window. On the inspection app, no signal was the normal case for auditors, not an edge case. If a request reaches your service, the response is lost, and the phone only reconnects after 24 hours, the cached response has expired and the retry looks new. Does the service keep the key itself longer than the response, so it can still reject the duplicate?