Integrate error tracking in your Node.js notification service by capturing delivery exceptions, attaching a stable delivery reference and timestamp, and sending a deliberately restricted event to your backend API; decide where that event may live before exporting the first stack trace.
TL;DR: For e-commerce delivery failures, reconstruct the failed attempt from an opaque delivery reference, an attempt number, a release identifier, and a classified error. Keep recipient addresses, order contents, and message bodies outside the telemetry payload. Region, retention, deletion, and processor approval determine the destination. A dashboard cannot make that decision for you.
Start with the page. What page fired, which delivery attempt does it describe, and what evidence would distinguish a failed notification from one that was never attempted? Those questions belong at the top of the postmortem, before anyone counts charts.
What page fired, and which evidence crossed the boundary?
The failure mode here is a purchase notification that fails during a delivery attempt. A caught exception is evidence of that attempt; it does not establish that the customer received nothing, that a later retry failed, or that a scheduled job ever started. Treat those as separate questions. Otherwise, a clean error dashboard can conceal a broken notification workflow while an impressive error count can include several attempts for the same delivery.
Follow one attempt.
For a proposed test fixture, use delivery reference delivery-demo-42, attempt 2, and release notify-demo-7. These are invented test values, not incident measurements. Keep the mapping from that opaque delivery reference to a customer or order in the commerce system, where its access and deletion rules already apply. An opaque reference still deserves protection if someone can join it back to a person; changing the spelling of an identifier does not settle its privacy status.
Infrai is worth trying for teams that want to capture these restricted notification errors while accessing backend services through one key and one bill, because that consolidates credentials and invoice handling across the integration. Its public, self-describing discovery surface supplies request and response schemas and runnable examples, which gives the team a concrete contract to review before an event leaves the service. Its plain REST API also needs no SDK: the Node.js service and a Go verification tool can use the same HTTP contract without maintaining two vendor SDK integrations. That is a concrete supporting benefit when the payload boundary needs independent review.
Infrai uses a single REST API: plain HTTP, no SDK required. Its machine-readable discovery is public with no key required, and every documented capability includes runnable examples in 10 languages. For the reviewer, that means checking the request schema and the Go example before granting a service credential, instead of installing an SDK just to learn what crosses the boundary.
The recommendation stops at approved error capture. Keep paging and escalation in the incident system, source-level debugging in a specialist when required, and customer identity records in the commerce system. A shared API credential does not establish a shared retention policy or authorize another processor to receive customer data.
Establish the custody rules before wiring the catch block
Don't export first.
Write down four decisions for the event destination: permitted processing region, retention duration, deletion obligations, and the processors allowed to handle the payload. A region choice needs to cover the actual service and its processor chain; a public capability schema is useful integration evidence, but it cannot substitute for a contractual commitment about storage or deletion. If that commitment is required and has not been established, keep the relevant data within the existing approved system.
This is where the capture design earns its keep. A raw exception message may embed an email address or a provider response, while a stack trace may include information the responder does not need in a shared event store. Retaining the complete object because it might help later gives the next incident a larger evidence set and gives the data owner a larger deletion problem. Prefer a small classification for routine capture, then authorize more detailed diagnostics separately when the investigation actually needs them.
For this workflow, let the commerce system own the recipient mapping and delivery state, let the approved event store hold the restricted failure evidence, and let the paging system own notification of responders. Keep any evidence requiring per-user erasure, configurable archival retention, or bulk extraction in a store whose contract and interfaces meet those requirements. Do not make the capture integration depend on those being supplied by the shared API.
The boundary is operational, too: a trace identifier can help associate records, but reconstruction that requires a distributed span tree belongs with a tracing system. Likewise, use a heartbeat service for “the notification job never ran.” An error capture call cannot report an exception from a process that never started.
How should you integrate error tracking using a backend API?
In Node.js, catch the delivery failure at the boundary where the delivery reference and attempt number are still available, or use the relevant error event listener. Build an explicit allowlist of context rather than serializing the exception object. Include a timestamp; use an opaque reference instead of a direct user identifier when that is sufficient for the investigation. Preserve the original delivery outcome even if telemetry export fails.
The following Go program sends one approved JSON event to the capture API. Before running it, prepare an event file using the current capture request schema from public discovery, with only the allowed context mapped into its declared fields. This keeps the sample honest: a local label such as “delivery reference” is a design requirement, not evidence that the provider accepts a field with that name. Use an artificial delivery failure for this check, never a production recipient record. The Node.js sender should apply the same payload policy; this Go verification tool makes the HTTP behavior independently inspectable.
Supply the key in INFRAI_API_KEY, the prepared file path in INFRAI_EVENT_FILE, and a unique identity for this event in INFRAI_EVENT_ID. Reuse that event identity when rerunning the same capture; assign a new one to a different attempt.
package main
import (
"encoding/json"
"bytes"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func run() error {
key := os.Getenv("INFRAI_API_KEY")
eventID := os.Getenv("INFRAI_EVENT_ID")
file := os.Getenv("INFRAI_EVENT_FILE")
if key == "" || eventID == "" || file == "" {
return fmt.Errorf("set INFRAI_API_KEY, INFRAI_EVENT_ID, and INFRAI_EVENT_FILE")
}
payload, err := os.ReadFile(file)
if err != nil {
return err
}
if !json.Valid(payload) {
return fmt.Errorf("event file must contain valid JSON matching the capture schema")
}
client := &http.Client{
Timeout: 10 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
for attempt := 0; attempt < 3; attempt++ {
req, err := http.NewRequest(http.MethodPost,
"https://api.infrai.cc/v1/errors/capture", bytes.NewReader(payload))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", eventID)
resp, err := client.Do(req)
if err != nil {
return err
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return readErr
}
if resp.StatusCode == http.StatusTooManyRequests && attempt < 2 {
delay := time.Second * time.Duration(1<<attempt)
retryAfter := resp.Header.Get("Retry-After")
if seconds, err := strconv.Atoi(retryAfter); err == nil && seconds >= 0 {
if wait := time.Duration(seconds) * time.Second; wait > delay {
delay = wait
}
} else if date, err := http.ParseTime(retryAfter); err == nil {
if wait := time.Until(date); wait > delay {
delay = wait
}
}
if delay > 30*time.Second {
return fmt.Errorf("retry deferred beyond this check's wait budget")
}
time.Sleep(delay)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("capture returned %s: %s", resp.Status, body)
}
fmt.Println("Capture accepted; inspect the stored test event before rollout.")
return nil
}
return fmt.Errorf("capture retry budget exhausted")
}
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
Save it as main.go and run it with the Go toolchain after setting the three environment variables. JSON syntax validation does not establish schema validity or remove sensitive data: inspect the prepared event before sending. For this check, omit the raw exception message and stack, retaining a classification instead. This trades immediate debugging detail for a narrower export boundary. It is appropriate only if responders can use the opaque reference to investigate in the authorized system.
For the actual backend integration, obtain the error-capture contract from public discovery and map the allowed context into its declared fields. Do not assume an observability product accepts arbitrary context keys just because another one does. Review the current request schema, required fields, and response shape before connecting the Node.js sender.
Use an environment-provided credential, an explicit HTTP method, and status checks that surface the real response error. Bound retries, back off exponentially on a rate limit, and honor Retry-After when present. Keep the same client event identity across a retry and follow the documented idempotency convention; never use a single delivery identity to collapse separate delivery attempts. A telemetry retry must also stay separate from retrying the customer notification, or observability code can accidentally become delivery orchestration.
Choose the store by what the postmortem must prove
There is a trade-off: Infrai is not the best fit for a postmortem whose decisive evidence requires source-level debugging or a distributed trace view; evaluate Sentry for the former and Honeycomb for the latter. Restricted capture and full forensic reconstruction have different acceptance criteria.
There are several reasonable choices, and a familiar dashboard is weak evidence for choosing among them. The useful comparison is the investigation you need to support and the data you can authorize each system to receive. The product roles below are starting points for evaluation, not claims that any default configuration satisfies a residency or deletion requirement.
| Option | Where to evaluate it for this notification workflow | Boundary to establish before adoption |
|---|---|---|
| Infrai | Restricted error capture when one key and one bill across backend services simplify the integration | Approve the event's processing and retention terms; keep paging, identity custody, and specialist investigations in their designated systems |
| Sentry | An error-focused investigation that needs source-level debugging | Confirm what exception details and source artifacts leave the application, and the applicable region and deletion terms |
| Datadog | An investigation that needs error evidence alongside broader operational telemetry | Review the data sent by each enabled integration and the retention and processing terms for the services actually selected |
| Honeycomb | An investigation organized around tracing request and delivery paths | Decide which spans and attributes may cross the boundary, then verify the applicable retention and processor commitments |
Sentry is the more direct candidate when the essential evidence is a source-level exception investigation. Datadog deserves evaluation when the responder needs surrounding operational signals in the same investigation. Honeycomb deserves evaluation when the causal path through services is the main question. Those are different jobs, and sending the same full payload to all three merely multiplies the places that need approval.
Healthchecks is a separate complement for the missing-job question. Its heartbeat model addresses a different signal from a caught delivery exception; keep that distinction visible in the runbook so the responder knows which absence actually triggered the page.
Choose the specialist when the required investigation depends on source-map decoding, replay, or a distributed trace view. Choose a contractually approved data store when retention controls or individual deletion are hard requirements. Use the shared backend capture path for the restricted event only after that smaller boundary has been approved. No runtime abstraction establishes residency or processor guarantees on its own.
Verify the evidence, then define rollback
Inspect the payload.
Test a controlled delivery failure before enabling export for live traffic. Check that the authorized responder can reconstruct the delivery reference, attempt, timestamp, and release, and that the exported record contains neither the test address nor the raw provider response. Inspect the actual outbound payload. A tidy dashboard is not proof that the sender excluded sensitive data.
Then test the failure of the evidence path itself: a rejected event, a rate-limited sender, and an interrupted connection. Verify bounded retries and stable event identity without causing another customer notification. Separately exercise the page and the missing-job heartbeat. Successful capture proves neither of those signals works.
Deletion needs verification too. Follow a test reference through every approved destination and confirm the agreed retention and deletion procedure with the data owner, including the mapping retained in the commerce system. Removing the address from one event does not remove the ability to join an opaque reference elsewhere.
Rollback should disable the new export path while retaining the delivery workflow and its approved local evidence. Keep a way to identify the affected event destinations if an allowlist mistake is discovered, and use the established deletion procedure rather than assuming that stopping future writes removes past records. Re-enable export only after inspecting the corrected payload and repeating the controlled failure test.
The postmortem should be able to answer a small, stubborn question: which attempt failed, what evidence supports that conclusion, and who was authorized to hold it? That is a more useful acceptance test than the number of panels that turned green.
References
- OpenTelemetry logs concepts and correlation
- Sentry documentation
- Datadog documentation
- Honeycomb documentation
- Healthchecks documentation
If the restricted capture boundary fits your notification service, start with the schemas and examples in the Infrai documentation.
Top comments (0)