Retry code rarely looks like infrastructure when it is first written.

It looks like five lines around an HTTP request.

Call the endpoint. Catch the exception. Wait. Try again.

For simple integrations, that may be all you ever need.

The problem starts when the event matters and the environment is unreliable.

Then the retry loop needs memory.

The first retry is easy

Suppose an IoT application produces an event that needs to reach the cloud.

The first version might be:

  1. send event
  2. if request fails, wait five seconds
  3. try again

Then someone asks what happens if the application restarts during those five seconds.

So the event is written to disk.

Now the application needs to know which stored events are pending and which have already been delivered.

Then the cloud returns 429.

Fixed five-second retries are no longer appropriate, so you add exponential backoff.

Then the request succeeds but the acknowledgement disappears.

Now a retry may create a duplicate.

The event needs a stable ID.

None of this is bad engineering. It is the normal consequence of taking delivery seriously.

Delivery state does not belong to the business domain

The application probably exists to do something specific.

Control a machine. Read sensors. Operate a kiosk. Track a vehicle. Run a production process.

Backoff intervals, reconnect scheduling and replay state are not part of that domain.

They appear in the code because the application has become responsible for delivery.

That responsibility can be reasonable. For a small system, keeping everything in one process may be the simplest solution.

But it should be a conscious trade-off.

The application now owns an operational subsystem.

Offline makes the boundary obvious

When connectivity disappears for several hours, immediate retries stop being useful.

The event needs durable local storage.

New events continue to arrive.

The application now has a backlog.

When connectivity returns, it needs to drain that backlog while still accepting new events. If the connection disappears again, it needs to resume later.

A feature that started as retry logic now includes:

  • local persistence
  • scheduling
  • backoff and jitter
  • event identity
  • duplicate handling
  • restart recovery
  • reconnect logic
  • backlog draining
  • storage limits
  • operational visibility

At that point, it is reasonable to ask whether the application should continue owning it.

A local handoff is a useful boundary

One alternative is to give the application a much smaller responsibility.

Produce the event and hand it to a local durable delivery component.

The application does not wait for the cloud. It waits for local acceptance.

If the handoff succeeds, delivery responsibility moves.

The producer's code can become close to:

await queuey.PublishAsync("temperature.updated", payload);

The important part is not the SDK call itself.

It is what success means.

If the event is durably persisted before the call returns, the application can stop carrying its own delivery state.

Queuey Edge uses that model.

Retries still exist

Moving retry logic out of the application does not make retries disappear.

The delivery layer still has to deal with them.

It needs to distinguish transient failures from conditions that should not be retried immediately. It needs to back off. It needs to recover after restart. It needs to handle lost acknowledgements and idempotent resend.

The difference is ownership.

One delivery component can solve those mechanics consistently instead of every application implementing its own variation.

That also gives operations one place to inspect what is pending and why.

There are good reasons to keep it simple

Not every application needs a delivery layer.

If an event is low-value and another update will arrive in a few seconds, an in-memory retry may be enough.

If the device is permanently connected inside a controlled network, offline persistence may solve a problem you do not have.

Infrastructure has a cost too.

The point is not that application-level retries are wrong.

The point is to notice when they have stopped being “a retry” and become a system you are responsible for operating.

A useful code review question

When delivery code changes, ask what happens in four situations:

  • the network disappears for six hours
  • the process restarts with events pending
  • the receiver gets the event but the ACK is lost
  • the backlog becomes larger than local storage allows

If the answer requires more application state each time, the delivery responsibility may be in the wrong place.

The cleanest code is not always the code with the fewest lines.

Sometimes it is the code with the fewest responsibilities.