Shipmind Labs

Our admin screen said the notifications had gone out. All that had really happened was that enqueue returned a ticket.

In notify-dispatch, our open-source notification library, the outbox is thin on purpose. Enqueue takes the recipient, the template and a copy of the context, stamps the time from the outbox clock, appends the ticket to an in-memory deque, and hands it back. Nothing is rendered. No dedup key is claimed. No provider is touched.

Everything that decides the outcome runs later, in drain, on whichever thread calls it, because drain just performs the ordinary send. The retry policy, the dead-letter queue, the dedup claim and the quiet-hours check all evaluate at that moment, not at enqueue.

We learned what that means the hard way, with a worker thread that wasn't running in one deployment. Tickets piled up, the screen kept reporting success, and when the process restarted the deque went with it. The queue is memory-only by design. We had been reading it as durable.

The second surprise came when we drained a backlog by hand. Quiet hours were read at the drain moment, so messages queued before the window were judged by the clock after it. Dedup keys were claimed in drain order, which is not necessarily the order anyone intended.

Three things changed.

A ticket is a handle. It is not a promise. Our interfaces now show ticket state (pending until the settle event fires, then sent or failed) instead of treating a returned ticket as delivery.

When the enqueue moment is the policy-relevant moment, we queue that timestamp explicitly, so drain doesn't re-decide it against a later clock.

Anything that must survive a restart is written to durable storage before it is enqueued. The receipt, or the dead letter, is the audit record. The enqueue call is not.

If you run something similar, it is worth knowing where your system draws the line between accepted and sent, and whether the caller is ever told which one it got.

Was this useful?

Building something similar?

or email hello@shipmindlabs.com