Customer support needs SMS delivery status events fetched via API to decide whether an order receipt should be sent again after payment settles. The constraint is evidentiary: a payment record, an accepted notification request, and a delivery observation answer different questions, so combining them into one success flag makes that decision difficult to defend.
Short answer: fetch SMS delivery status events through the messaging API, preserve the returned evidence against the original message identifier, and reconcile it with the settled payment before authorizing another receipt. For a Node.js integration, first establish the API's authentication, response shape, and status semantics. The Go example below demonstrates that HTTP boundary with an explicitly defined adapter contract; it is not a provider-specific Node.js SDK example.
Require three checks: settlement, identity, and evidence.
What do SMS delivery status events prove about a receipt?
Begin with a proposed support policy: a receipt becomes eligible for dispatch only after the payment system records settlement. That policy makes the settlement record the authority for financial eligibility, while the notification record supplies evidence about communicating the receipt. A status response must not be allowed to manufacture a settlement or silently amend its amount, currency, or order association.
Keep those authorities separate.
For the running example, use illustrative identifiers: order order_260, settlement settlement_260, and notification attempt receipt_260_attempt_1. These are local design choices, not fields promised by any public messaging API. Persist the association before fetching subsequent status observations, and require the returned message identifier to match the stored attempt. A successfully parsed response for a different message is still the wrong evidence.
The remaining question is semantic. Define which documented observations your integration will treat as sufficient to close a support inquiry, which justify a human review, and which leave the outcome unresolved. Do not derive those meanings from the spelling of a status string. Until the relevant API documentation establishes its meaning, a field named delivered is unsuitable as proof that a particular customer read a particular receipt.
This is where the exactly-once mindset belongs: specify which local business transition must happen once, and identify the evidence required to authorize it. A status fetch alone cannot establish that an external communication happened exactly once.
Derive the fetch boundary from the evidence you need
Use a narrow adapter so the support workflow consumes a stable internal shape while the integration translates the selected API's documented response. The proposed contract here is a JSON object with a message_id and an events array; each event contains an opaque status string. The adapter is responsible for obtaining that object. No commercial API is asserted to expose this schema.
The example fetches one snapshot of that contract. Its limitation is deliberate: it does not implement pagination, signing, callback verification, or a provider's status vocabulary. Resolve those integration requirements from the actual service documentation before deployment; choosing a snapshot interface makes the example small but places completeness checks in the adapter.
Under HTTP semantics, GET is a safe, idempotent method, as defined by RFC 9110. That describes the method's intended effect, not a guarantee that repeated reads return identical status observations. Keep retries of a read distinct from authorization to send another receipt.
package receipts import ( "context" "encoding/json" "fmt" "io" "net/http" "time" ) type Event struct { Status string `json:"status"` } type Snapshot struct { MessageID string `json:"message_id"` Events []Event `json:"events"` } // Fetch reads a trusted, configured adapter URL, not a customer-supplied URL. // Its contract is a complete JSON snapshot for one message. func Fetch(ctx context.Context, endpoint, token, wantID string) (Snapshot, error) { var snapshot Snapshot req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return snapshot, err } req.Header.Set("Authorization", "Bearer "+token) req.Header.Set("Accept", "application/json") client := &http.Client{ Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }, } resp, err := client.Do(req) if err != nil { return snapshot, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return snapshot, fmt.Errorf("adapter returned HTTP %d", resp.StatusCode) } const maxBytes = 1 << 20 body, err := io.ReadAll(io.LimitReader(resp.Body, maxBytes+1)) if err != nil { return snapshot, err } if len(body) > maxBytes { return snapshot, fmt.Errorf("adapter snapshot exceeds size limit") } if err := json.Unmarshal(body, &snapshot); err != nil { return Snapshot{}, err } if wantID == "" || snapshot.MessageID != wantID { return Snapshot{}, fmt.Errorf("message identifier mismatch") } return snapshot, nil } The 10-second timeout and 1 MiB response limit are example operating limits, not benchmarks or API limits. Select them against actual response sizes and the support latency budget. Configure an HTTPS endpoint controlled by the integration, obtain the token from secret storage, and exclude both credentials and receipt contents from diagnostic output.
An empty event array remains an empty observation set.
It does not authorize another send.
Likewise, the returned strings stay opaque until a documented mapping supplies their meaning; preserving an unknown value for review is preferable to assigning it an invented terminal state.
Preserve the audit trail before changing support state
After a successful fetch, append an observation record containing the local attempt identifier, fetch time, message identifier, source status, and the version of the mapping used to interpret it. If the source supplies event identifiers and timestamps, retain them with their documented meanings. Distinguish the time your system observed a response from any time the source says an event occurred. That distinction matters during reconciliation, because a later fetch is later evidence collection, not necessarily evidence of a later delivery transition. Define ordering from the documented source contract. When ordering cannot be established, retain the conflicting observations and route the ambiguity to support instead of silently choosing whichever response arrived last.
For local idempotency, use a documented event identifier when one exists. Otherwise, explicitly decide whether repeated snapshots should remain separate fetch records or whether an internal fingerprint can identify equivalent observations. Neither policy should discard evidence merely because two different events carry the same status string.
Commit the observation and the resulting local support-state transition together. Keep receipt dispatch behind a separate durable authorization record with a uniqueness rule for the intended business action. This adds persistence work, but it makes a repeated poll distinguishable from a deliberate second send. A replay test should show one local authorization for the same approved action.
Auditability also has a boundary. This design does not establish compliance with any particular retention, privacy, or payment obligation. Set retention and access rules through the applicable review, and avoid copying full receipt bodies or destination phone numbers into every observation merely to make debugging convenient. SPF, defined in RFC 7208, concerns email sender authorization; it supplies no authority for interpreting SMS delivery status.
Compare integration effort after defining correctness
The useful comparison is the amount of behavior the team must own to answer a support inquiry accurately. These options describe implementation choices, with requirements to verify rather than universal claims about messaging services.
| Approach | Integration work | Boundary to establish |
|---|---|---|
| Direct status reads | Authentication, response mapping, retry policy, and persistence | Does the documented response provide a current snapshot or event history? |
| Callback ingestion | Endpoint verification, durable intake, replay handling, and mapping | How are authenticity, retries, ordering, and event identity documented? |
| Callback intake plus reconciliation reads | Both paths and a shared evidence model | Can observations from both paths be correlated without losing their meaning? |
For a narrowly scoped support tool, a direct read may reduce the initial endpoint and intake work, provided the documented read API exposes the evidence the workflow needs. The trade-off is that the team must schedule retrieval and define how long support can wait for the next observation. Callback intake changes that work into an inbound processing obligation. A combined design requires more integration effort and should earn it through a concrete reconciliation requirement.
Evaluate operating cost through the chosen polling frequency, retained evidence volume, and review burden. Unit pricing is unnecessary to decide whether the integration can explain a second dispatch.
Roll out against settlement and replay cases
Start with a shadow workflow that records observations without authorizing receipt dispatch. Test an unsettled payment, a mismatched message identifier, an empty event array, an unknown status, malformed JSON, and a repeated observation. These are proposed test cases, not reports of production incidents. Confirm that none can accidentally create a second dispatch authorization.
Then enable a limited cohort with a reviewable repeat-dispatch policy. Monitor unresolved attempts, age since the last successful fetch, mapping failures, and duplicate authorization attempts; choose alert thresholds from the support workflow's operating requirements. Deployment review should include the mapping version and a way to disable new dispatch authorizations while preserving evidence collection.
The acceptance criterion is concrete: support can trace a receipt decision back to a settled payment, the correct notification attempt, and the observations used to justify that decision. Expand the rollout only after replay and reconciliation preserve that chain.
References
- RFC 9110, HTTP semantics, including safe and idempotent methods: https://datatracker.ietf.org/doc/html/rfc9110
- Go standard library, HTTP clients, requests, and timeouts: https://pkg.go.dev/net/http
- Go standard library, JSON decoding: https://pkg.go.dev/encoding/json
- Go standard library, bounded readers: https://pkg.go.dev/io#LimitReader
- RFC 7208, Sender Policy Framework for email sender authorization: https://datatracker.ietf.org/doc/html/rfc7208
Sources
The References above identify the protocol and standard-library sources. The receipt policy, identifiers, adapter contract, and rollout criteria are proposed design choices.
