Build1 publisher3 min readPublished
A stable key per notice and channel lets a restarted worker resend only the pending SMS
A dev.to design post writes an attempt row before every provider call and keeps accepted apart from delivered across six states, paying extra latency where the API publishes no idempotent-send contract.
The Engineer · Build desk

What happened
- Its worked case has account acct_7F3 trigger notice terms-v4 at 09:00, the email adapter record acceptance on attempt one, the SMS adapter ask the dispatcher to wait, and the worker restart at 09:02.
- The accept-then-crash gap is handled by a stable idempotency key passed through the adapter where the API supports one, plus a local uniqueness constraint on the notice and channel pair.
- Rate limits are treated as scheduling data: the dispatcher uses a server-provided delay when the adapter exposes one, and otherwise capped exponential backoff with jitter.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint Transaction discipline cannot remove the ambiguous window, because the provider call happens outside the database, so the containment has to live in the schema as a uniqueness constraint.
- decision Picking a retry count becomes an ownership question: the notice deadline, the provider's published rate-limit behavior and the escalation window have to be configuration with named owners.
- capability An auditor can reconstruct one notice from one table, since a delivery update arriving hours later appends a transition and the earlier acceptance record stays intact.
The crash window is the part of this design that cannot be argued away. A worker calls the provider, the provider accepts, and the worker dies before it writes `accepted` locally. The local transaction cannot cover it: the network call happens outside the database [16]. The defense the post proposes has two parts, a stable idempotency key passed through the adapter when the selected API supports one, and a local uniqueness constraint on `(noticeId, channel)` [17]. Elsewhere the post recommends serializing attempts for that key and reconciling ambiguous outcomes before sending again, and it says that costs latency [18]. "It also avoids pretending distributed uncertainty vanished," the author writes [19]. A single Boolean gives two states. The post names six, each transition stamped with a time and a reason [8]. Six states minus a Boolean's two leaves four distinctions a `sent` flag cannot express [27]. The pair that causes trouble at review is the first one: an API can accept work before the channel reports its final outcome, so one bit forces two facts into one [7]. The ledger also keeps the original business event ID apart from the provider receipt ID, on the grounds that they describe different boundaries [6]. Destination fingerprints go in the row instead of raw addresses when operators do not need the address itself [5]. Two minutes after the trigger in the worked example, the notice is neither simply sent nor failed, because it has two channel records with independent clocks [30][28]. The restarted worker reads the email terminal state, leaves that channel alone, and enqueues only the pending SMS attempt under the same stable key [10]. A later email delivery update appends another transition, and the original acceptance record stays as written [11]. The operator reviewing the case reads the event ID, content revision, destinations, attempts, delays and final outcomes in order; "No log correlation ritual is required," the author writes [12]. Retry policy lives in the dispatcher. The adapter returns a typed outcome that separates acceptance, a retryable delay and a permanent rejection, and the post says that keeps the policy testable without sending anything [13]. When the adapter exposes a server-provided delay, use it; otherwise capped exponential backoff with jitter, where the cap stops a busy destination vanishing for an absurd interval and the jitter stops a fleet of workers waking together [14]. Retries have to terminate, since a compliance team needs a terminal state and an escalation path [15]. On the count itself the author writes: "I'm not sure any generic retry count is defensible without the notice deadline, the API's published rate-limit behavior, and the team's escalation window." [20] The same post is equally clear about what it measures. "I don't benchmark vendor throughput in the happy path first; I benchmark queue age under throttling, because that is where an apparently tiny integration starts growing glue," the author writes [22]. For that to be the right instrument on your system, throttling has to be the binding limit and the notice has to have a deadline; a provider that never throttles you produces a flat graph and no information. Two standards get pushed back to their own scope. RFC 7489 describes domain-based message authentication, policy and reporting for email, and that is a different job from an application delivery ledger [23]. The WebOTP API obtains a specially formatted one-time password from an SMS message with user consent, and is not a general receipt mechanism for compliance notices [24]. This is a design argument with a hypothetical sequence, and the post does not report production numbers [29]. Its stated cost is one queue and one ledger, in exchange for keeping rate-limit handling out of the customer-facing request and making duplicate suppression explicit [2].
What to watch
- Whether the email and SMS providers a team actually uses publish an idempotent-send contract; without one, the design falls back to serialized attempts and reconciliation.
- Queue age under throttling as the metric a team instruments first, since happy-path throughput will not surface the glue the post warns about.
- The published example uses only local interfaces, so the adapter that talks to a real provider is still to be shown.