Short answer: Keep the password-reset template and its expiry policy in the application, then use a Node.js adapter or equivalent worker to fetch SMS delivery status events, normalize them, and store each event idempotently.
The boundary matters.
Password reset SMS is a small message with an unpleasantly large boundary. In an education platform, I would keep the reset template under application ownership and treat delivery events as untrusted evidence about what happened after submission. That gives the application control over wording, locale, expiry policy, and audit identifiers, while leaving carrier-specific status interpretation at the integration edge.
The practical answer is to receive or fetch events into an idempotent consumer, verify the event's provenance according to the transport you selected, normalize only the fields your product needs, and store the original payload separately. Do not make a second password-reset attempt just because a status is delayed. A 10-minute token lifetime is an example policy, not a universal standard; the important point is that delivery processing must never extend it silently. A short-lived token and a durable event record solve different problems.
What should the application actually own?
A reset request should create an application record before it creates a message attempt. My minimum record has a non-secret reset identifier, a template revision, locale, requested expiry timestamp, message-attempt identifier, and a hash or reference for the destination. It does not store the reset token in an SMS event log.
Template ownership is the first architectural decision because it determines what can be tested before an event ever exists. With an application-owned template, a deployment can test that the message contains a short-lived link, the correct language, and no sensitive account details. It can also pin the revision in the reset-attempt row before the transport call begins, which gives a later status event something stable to reference even if the next release changes the copy. That extra record is easy to omit because delivery APIs tend to expose message status more prominently than message intent; the omission is discovered later, when support needs to explain why two learners saw different wording during one afternoon. With a provider-owned template, operational editing may be convenient, but the application must define how a template revision is discovered and how a later delivery event is tied back to the exact content that was intended.
That is a real trade-off, not a branding preference. Application ownership increases release responsibility. Provider ownership moves some editing responsibility outside the application and can make reproducibility harder. I prefer the former for password resets because the security and expiry rules belong next to token issuance.
How should Node.js code handle fetching SMS delivery status events?
An event is a report, not a command. It may arrive after the user has already requested a new reset, and a retry may produce a duplicate or an older status. The consumer therefore needs a stable event identity when the transport supplies one, a deduplication key when it does not, and an ordering rule that is explicit rather than implied by arrival time.
Here is a deliberately provider-neutral normalizer. The input shape is an internal contract for an adapter; it is not a claim about any particular API's field names.
from dataclasses import dataclass from datetime import datetime from typing import Any @dataclass(frozen=True) class DeliveryEvent: event_id: str attempt_id: str state: str observed_at: datetime raw: dict[str, Any] def normalize_event(payload: dict[str, Any]) -> DeliveryEvent: event_id = str(payload["event_id"]) attempt_id = str(payload["attempt_id"]) state = str(payload["state"]).lower() observed_at = datetime.fromisoformat(payload["observed_at"].replace("Z", "+00:00")) allowed = {"accepted", "delivered", "failed", "expired", "unknown"} if state not in allowed: state = "unknown" return DeliveryEvent( event_id=event_id, attempt_id=attempt_id, state=state, observed_at=observed_at, raw=payload, ) The adapter should fetch or receive the upstream event, validate its transport-level authentication, and pass only a parsed object into this function. The database transaction then inserts the event by event_id, updates the attempt only if the incoming observation is newer under the chosen ordering rule, and records the raw payload in restricted storage. If the same event is seen twice, the second transaction is a no-op. Keep it boring.
The easy mistake is to map every non-delivered value to “send again.” A temporary failure, a permanent failure, an expired attempt, and an event the adapter cannot understand need different policy. In a password-reset flow, the safe default for an unknown state is to keep the reset request usable only until its existing expiry and expose a retry action to the user; it is not to extend the token silently.
The retry decision belongs to the reset policy.
Which failure modes deserve a test?
I would test the event lifecycle as a partial-information system. The message request can succeed while the status lookup is delayed. A status can be duplicated. Events can be received out of order. The destination can be masked in logs incorrectly. A template revision can change between two deployments while an earlier attempt is still in flight.
The test data should include at least these transitions: accepted to delivered, accepted to failed, a duplicate delivered, failed followed by an older accepted, and an unrecognized state. For each case, assert both the current attempt state and the audit record. Testing only the happy delivery path proves very little.
Observability needs the same boundary. Log the internal attempt ID, event ID, template revision, and timestamps. Avoid logging the reset token, the full destination, or the message body. A metric for “events received” is useful; a metric for “events received and correctly correlated” is better. Alert on a growing age between request and terminal observation, but define terminal states carefully so a provider-specific intermediate label does not make the alert meaningless.
Does polling or receiving events change the ownership decision?
It changes the operational shape, not the ownership principle. A receiver needs authentication, replay protection, bounded work, and a retry path. A poller needs a cursor or time window, overlap to tolerate clock skew, and a deduplication table. Both need a reconciliation job because production systems lose messages, credentials expire, and operators eventually have to explain a missing status.
For a polling adapter, persist the last successful upstream position only after the local transaction commits. For a receiver, acknowledge only after durable enqueue or transaction completion, depending on the transport contract. These are small sequencing details; they decide whether a process restart produces a gap or a duplicate.
The application should also keep its own template revision in the attempt record. That lets support answer a narrow question without reconstructing history from a mutable dashboard: which reset wording did this learner receive, and which expiry rule was applied? The answer matters even when the delivery event itself contains no message body.
A compact rollout rule for an education platform
Start with one template revision, one normalized state model, and a replayable event table. Run the adapter against recorded fixtures that contain duplicates, delays, unknown states, and malformed timestamps. Release the consumer separately from the reset-message change, then compare correlated event age and failure classifications before changing user-facing retry behavior.
My decision rule is straightforward: own the security-sensitive template and expiry policy in the application; isolate transport-specific event parsing in an adapter; preserve raw evidence; and make every state transition idempotent. The system can then change its notification transport without changing the meaning of a password reset.


