Skip to main content
Blog

Designing for Patchy Connectivity: Lessons From Building in Nairobi

We built Papyrus with the assumption that the network will fail at the worst possible moment. Here's what that looked like in practice.

Designing for Patchy Connectivity: Lessons From Building in Nairobi

If you build software in Silicon Valley, you treat the network as a substrate — it's there, it works, you trust it. Build in Nairobi (or in Eldoret, or in a village in Kakamega), and you learn quickly: the network is a condition, not a constant.

This post is about the architectural decisions we made because of that.

Decision 1 — Offline-first, not offline-tolerant

Most “offline-capable” apps treat offline as an exception: the app works online, and we degrade gracefully when offline. Papyrus inverts this: every interaction is designed assuming it might happen offline, with online as the commit step.

The mobile app caches recent documents locally. Approvals are queued locally. Uploads are queued locally. Comments are queued locally. The “submit” button never fails — it just enqueues.

Decision 2 — Aggressive client-side validation

When a user hits submit and the network is down, you cannot tell them “the server says X is required”. You have to validate everything client-side first. We invested heavily in shared validation logic that runs identically on client and server.

Decision 3 — Bandwidth-aware mode

When the user's connection is slow (we measure RTT and bandwidth continuously), the app:

  • Shows compressed thumbnails instead of full-resolution previews
  • Lazy-loads document content (page-at-a-time for PDFs)
  • Defers non-essential sync (notifications, analytics) until faster connection
  • Disables auto-play of any embedded media

Users on EDGE/3G should still feel the app is fast.

Decision 4 — Idempotency keys everywhere

When the network is unreliable, retries are constant. Every mutation in our API carries an idempotency key. The same operation can be retried safely without creating duplicates. This is critical for things like document uploads — partial success without idempotency creates phantom documents.

Decision 5 — The “syncing” indicator is honest

Don't lie to users about sync state. If their data hasn't reached the server, tell them. Our sync indicator shows:

  • ☁️ Synced
  • 🔄 Syncing (active operation in flight)
  • ⏸️ Offline (queued; will sync on reconnect)
  • ⚠️ Error (something needs your attention)

We never hide a queued operation behind a green check.

Decision 6 — Real-time is best-effort

SignalR for in-app live updates is aspirational. When the connection drops, we fall back to periodic polling. When that fails, we fall back to “fetch on next user interaction”. Users should never see stale data; they should sometimes see slightly delayed updates.

Decision 7 — Conflict resolution is a UX problem, not just a sync problem

When two users edit the same document offline and both come back online, you have a conflict. The wrong answer is to silently pick one and discard the other. The right answer is to surface the conflict to the user(s) with a clear “this is yours, this is theirs, here's how to resolve” interface.

We learned this the hard way. Twice.

Decision 8 — The desktop agent is a circuit breaker

For high-volume document capture, the Desktop Agent is more reliable than the web upload. It has local persistence (SQLite queue), retry logic, and survives PC reboots. We recommend it to any user uploading more than 20 documents per day.

Decision 9 — Background sync is a feature

Modern browsers support background sync via service workers. We use it for the PWA so that even when the tab is closed, queued operations can complete when the network returns.

Decision 10 — Test in the worst conditions deliberately

We run a “Bad Network” testing day every two weeks. Engineers run the app with Chrome's network throttling set to “Slow 3G” with 30% packet loss. If anything is unusable in that mode, it gets escalated.

What this isn't

It isn't a critique of “first-world software”. It's a recognition that the design constraints of the environment shape the product. Software built for unreliable networks is also more robust on reliable ones. The opposite is rarely true.

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.