Most analytics integrations capture plenty of events. The ones you can trust are the ones where those events reliably arrive — even when a user closes the tab mid-action, the network drops for a second, or a launch sends a spike of traffic. That last mile is invisible when it works and corrosive when it doesn’t: silently missing conversions make every report a little bit wrong. Here’s the delivery machinery inside the Pug Web SDK, and the failure modes each part is there to handle.
Batching: fewer requests, less overhead
Sending one HTTP request per event is wasteful — it hammers the network, drains battery on mobile, and adds latency. The SDK buffers events and flushes a batch when either trigger fires: the batch reaches its size limit (10 events by default) or a timer expires (5 seconds by default). Both are configurable. The result is a steady trickle of small batches instead of a storm of single requests — and you never write a line of it.
The page-unload problem, and sendBeacon
The hardest events to deliver are the ones fired right before the page goes away — the click that navigates, the
action just before someone closes the tab. A normal fetch started at that moment is often cancelled by
the browser. So on visibilitychange and pagehide, the SDK switches to
navigator.sendBeacon, which the browser is specifically designed to deliver even as the page unloads.
The batch is sent as binary protobuf, so it’s compact and fast. That’s the difference between capturing a user’s
last action and losing it.
A queue that survives reloads
Buffered events don’t live only in memory. The queue is backed by localStorage (with an in-memory
fallback if storage is unavailable), with debounced writes to stay cheap. If a tab closes or the browser crashes
before a batch is sent, those events are still in the queue on the next page load — they’re picked up and delivered
rather than lost. Reliability shouldn’t depend on the user keeping your tab open.
Lock, commit, rollback
When a batch is sent, the queue uses a two-phase protocol so events are never dropped or double-counted on failure. Sending locks (reserves) the events being flushed; a successful send commits, removing them; a failure rolls back, returning them to the queue for a later attempt. An in-flight batch can’t silently vanish, and a retry can’t duplicate what already succeeded.
Retries that know when to stop
Not every failure deserves a retry. The SDK classifies errors: transient ones (timeouts and
retryable server responses) roll back and try again, while permanent ones (a malformed or rejected
request) are not retried — retrying them would only flood your backend with requests that can never succeed. For the
events you can’t afford to delay, an immediate flag attempts a direct send right away and falls back to
the queue if the network is briefly down:
track('purchase', { amount: 49, currency: 'USD' }, { immediate: true }) Never throws, never blocks
Analytics should never be the reason your app breaks. track() is fire-and-forget and
never throws — every send is wrapped, and when something can’t be sent it’s logged with the
property at fault, not raised into your code. Events are also validated against their schemas before they leave the
browser, so a malformed event is dropped with a clear log instead of quietly polluting your dashboard. Each
autocapture listener is isolated in its own try/catch, so one misbehaving page element can’t take down the rest of
your tracking.
A clean lifecycle
The transport moves through a small, predictable lifecycle — idle, flushing, destroyed. Calling
destroy() on a single-page-app route change tears everything down cleanly: an in-flight batch is allowed
to finish, no further flushes are scheduled, and the SDK can be re-initialized fresh. No leaked timers, no orphaned
listeners, no duplicate sends.
Reliable, and yours
All of this runs against a backend you can own. Pug is open source and self-hostable — one Go binary, your events on your own servers — so reliable delivery doesn’t mean handing your data to someone else’s cloud. See the SDKs to get started, the Web SDK overview for the full feature set, and the platform page for how events flow from the SDK to insights.