Retry queue for sends that never reached the provider

A send that failed at the network level is persisted and replayed on a backoff, while a send the provider actually rejected is never retried.

What it is

message_outbox holds the original send options for retryable failures. A cron drains it: claims due rows with a compare-and-set so overlapping runs cannot double-send, reclaims 'sending' rows stuck over 15 minutes, replays through the same port facade with a fromOutbox flag, and backs off 5 minutes × 2^attempts up to six attempts before marking the row dead.

Also called: message outbox · failed send retry · network blip · queued messages

See it
Retry queue for sends that never reached the provider
CustomerBuildingAmountStatus
Ronan PetschRiding Arena$7,090Open
Gideon AltEquipment Storage$14,880Sent
Sutter KlineEquipment Storage$13,740Open
Sutter Kline30×40 Garage$6,140Open
Ronan PetschBarndominium$17,540Paid
A queue view with attempt counts and next-attempt timestamps, one row in 'dead' state showing its reason. Sample data — no customer information appears here.
How it works
  1. 1A retryable failure (network exception) enqueues the original options.
  2. 2The cron claims pending rows and stale 'sending' claims.
  3. 3Each is replayed through sendCustomerEmail / sendCustomerSms with fromOutbox set, so a still-failing send updates the row rather than enqueueing a duplicate.
  4. 4A send that comes back 'skipped' is marked dead with the reason — retrying cannot fix configuration.
  5. 5Rows that exhaust attempts become 'dead' and stay visible for ops.
Why we built it

This is one of two deliberate behavioural deltas the ports refactor introduced, recorded in customerMessaging.ts: 'Retryable failures (network-level, request never reached the provider) are persisted to message_outbox instead of being silently lost.' The strict definition of retryable is the safety half — an HTTP rejection means the provider saw the message, so retrying risks texting a customer twice.

The problem
  • Transient network failures silently lost customer messages.
  • Naive retrying would duplicate messages the provider had already accepted.
  • A crashed drain run could strand rows in a claimed state forever.
Sound familiar?
What you get
A blip costs a few minutes of delay, not a lost message.
Customers never receive duplicates from a retry.
Permanently broken sends stop retrying and stay visible.

See it on your own jobs

Twenty minutes, your numbers, no slide deck. We’ll build one of your real buildings in front of you and send you the estimate link at the end — yours to keep either way.

or keep browsing features →