<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/an-offline-outbox-that-never-loses-what-someone-typed-in-javascript-and-swift-3cilawnk9" -->

---
title: An offline outbox that never loses what someone typed,...
description: A deep technical walkthrough of the write queue ('outbox') behind an offline-first trail app, Hatchure, covering both its JavaScript web client and Swift...
canonical: https://daily.dev/posts/an-offline-outbox-that-never-loses-what-someone-typed-in-javascript-and-swift-3cilawnk9
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: An offline outbox that never loses what someone typed, in JavaScript and Swift | daily.dev
og:description: A deep technical walkthrough of the write queue ('outbox') behind an offline-first trail app, Hatchure, covering both its JavaScript web client and Swift...
og:url: https://daily.dev/posts/an-offline-outbox-that-never-loses-what-someone-typed-in-javascript-and-swift-3cilawnk9
og:image: https://api.daily.dev/og/posts/3CiLaWnk9.png
og:image:alt: An offline outbox that never loses what someone typed, in JavaScript and Swift
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# An offline outbox that never loses what someone typed, in JavaScript and Swift

**[Medium](https://daily.dev/sources/medium_js)** · 13 min read · 1 upvotes · 1 comments

## Summary

A deep technical walkthrough of the write queue ('outbox') behind an offline-first trail app, Hatchure, covering both its JavaScript web client and Swift iPhone client. It explains how the system distinguishes 'never sent' from 'sent, but the response was lost', classifies failures into offline, deferred, and rejected categories, writes pending changes to disk before attempting the network request, uses idempotency keys to prevent duplicate server-side writes, saves queue progress after every item rather than after a full pass, caps retries with a deferral counter, and ties queued changes to the user who made them. The piece distills these lessons into a seven-point checklist for building similar offline write queues in any language.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://levelup.gitconnected.com/an-offline-outbox-that-never-loses-what-someone-typed-in-javascript-and-swift-fc87b058293a>

## Questions this post answers

### How do I tell the difference between a network request that never reached the server and one where the response was lost?

Check whether the error carries a status code: a fetch rejection with no status means the request never reached the server at all and is safe to treat as offline, while a request that succeeded server-side but whose response body failed to parse should be tagged separately (an 'ambiguous success') so it is retried via an idempotency key rather than assumed to have failed.

_daily.dev surfaces practical write-queue patterns like this for developers hardening offline sync._

### Why would a retry queue cause duplicate records when retrying failed requests?

Duplicates happen when a request succeeds on the server but the client never receives or can't parse the response, so it assumes failure and resends the same data on the next flush. The fix is to send the queued item's own id as an idempotency key with every write, so the server can recognize a resend and return the existing record instead of creating a second one.

_Developers debugging duplicate writes in sync logic can track idempotency patterns like this on daily.dev._

### Should I classify every failed network request the same way in an offline retry queue?

No, failures should be split into three kinds: offline (no response at all, retry immediately on reconnect), deferred (server-side issues like a 503, rate limit, or full moderation queue - back off and retry later, starting at 30 seconds and doubling to a 15-minute ceiling), and rejected (a validation error or policy violation that would fail again if resent, so it gets parked for the user instead of retried).

_daily.dev helps developers compare retry and backoff strategies when designing resilient sync queues._

## Community discussion

Top comments from developers on daily.dev.

**@pdfopsdev** · 0 upvotes

> the 'ties queued changes to the user who made them' point is the one people skip, then wonder why a shared device replays someone else's edits. idempotency keys alone don't save you if you don't also scope them per user.

## Similar posts on daily.dev

- [Synchronize MySQL and IndexedDB in an Offline-First App](https://daily.dev/posts/synchronize-mysql-and-indexeddb-in-an-offline-first-app-t7qnof3ch) · Planet MySQL · 0 upvotes · 0 comments
- [Reliable HubSpot Sync with Outbox and QStash](https://daily.dev/posts/reliable-hubspot-sync-with-outbox-and-qstash-pc55isbcg) · Planet PostgreSQL · 0 upvotes · 0 comments
- [How to Fix the Dual-Write Problem in Node.js with the Outbox Pattern](https://daily.dev/posts/how-to-fix-the-dual-write-problem-in-node-js-with-the-outbox-pattern-kobnx54pc) · freeCodeCamp · 1 upvotes · 0 comments
- [How to Implement the Outbox Pattern in Distributed Systems](https://daily.dev/posts/how-to-implement-the-outbox-pattern-in-distributed-systems-0xhg3kran) · C\# Corner · 4 upvotes · 0 comments

---

Tags: [#webdev](https://daily.dev/tags/webdev), [#javascript](https://daily.dev/tags/javascript), [#swift](https://daily.dev/tags/swift), [#offline-first](https://daily.dev/tags/offline-first)

[View this post on daily.dev](https://daily.dev/posts/an-offline-outbox-that-never-loses-what-someone-typed-in-javascript-and-swift-3cilawnk9)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"An offline outbox that never loses what someone typed, in JavaScript and Swift","url":"https://daily.dev/posts/an-offline-outbox-that-never-loses-what-someone-typed-in-javascript-and-swift-3cilawnk9","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/an-offline-outbox-that-never-loses-what-someone-typed-in-javascript-and-swift-3cilawnk9"},"datePublished":"2026-10-10T16:21:45.595Z","dateModified":"2026-10-10T16:22:52.507Z","description":"A deep technical walkthrough of the write queue ('outbox') behind an offline-first trail app, Hatchure, covering both its JavaScript web client and Swift...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/21ab4bb3c86bf6abf6f7ba70a1697d8f?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/21ab4bb3c86bf6abf6f7ba70a1697d8f?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Medium","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Medium","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/medium","url":"https://daily.dev/sources/medium_js"},"commentCount":1,"discussionUrl":"https://daily.dev/posts/an-offline-outbox-that-never-loses-what-someone-typed-in-javascript-and-swift-3cilawnk9","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":1},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":1}],"keywords":"webdev,javascript,swift,offline-first","timeRequired":"PT13M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Medium","item":"https://daily.dev/sources/medium_js"},{"@type":"ListItem","position":3,"name":"An offline outbox that never loses what someone typed, in JavaScript and Swift"}]}
{"@context":"https://schema.org","@type":"WebPage","@id":"https://daily.dev/posts/an-offline-outbox-that-never-loses-what-someone-typed-in-javascript-and-swift-3cilawnk9","comment":[{"@type":"Comment","text":"the ‘ties queued changes to the user who made them’ point is the one people skip, then wonder why a shared device replays someone else’s edits. idempotency keys alone don’t save you if you don’t also scope them per user.","datePublished":"2026-10-10T18:05:07.406Z","url":"https://daily.dev/posts/3CiLaWnk9#c-MRiHDsUAb","author":{"@type":"Person","name":"PDFops","url":"https://daily.dev/pdfopsdev","image":"https://media.daily.dev/image/upload/s---8isRBKc--/f_auto/v1782922291/avatars/avatar_orjMeK8QKaaVZwGq7ScPz?_a=BAMAMicg0"}}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/an-offline-outbox-that-never-loses-what-someone-typed-in-javascript-and-swift-3cilawnk9#faq","mainEntity":[{"@type":"Question","name":"How do I tell the difference between a network request that never reached the server and one where the response was lost?","acceptedAnswer":{"@type":"Answer","text":"Check whether the error carries a status code: a fetch rejection with no status means the request never reached the server at all and is safe to treat as offline, while a request that succeeded server-side but whose response body failed to parse should be tagged separately (an 'ambiguous success') so it is retried via an idempotency key rather than assumed to have failed. daily.dev surfaces practical write-queue patterns like this for developers hardening offline sync."}},{"@type":"Question","name":"Why would a retry queue cause duplicate records when retrying failed requests?","acceptedAnswer":{"@type":"Answer","text":"Duplicates happen when a request succeeds on the server but the client never receives or can't parse the response, so it assumes failure and resends the same data on the next flush. The fix is to send the queued item's own id as an idempotency key with every write, so the server can recognize a resend and return the existing record instead of creating a second one. Developers debugging duplicate writes in sync logic can track idempotency patterns like this on daily.dev."}},{"@type":"Question","name":"Should I classify every failed network request the same way in an offline retry queue?","acceptedAnswer":{"@type":"Answer","text":"No, failures should be split into three kinds: offline (no response at all, retry immediately on reconnect), deferred (server-side issues like a 503, rate limit, or full moderation queue - back off and retry later, starting at 30 seconds and doubling to a 15-minute ceiling), and rejected (a validation error or policy violation that would fail again if resent, so it gets parked for the user instead of retried). daily.dev helps developers compare retry and backoff strategies when designing resilient sync queues."}}]}
```

