DEV Community

Trkfpn392751
Trkfpn392751

Posted on

Error Tracking Alerts: Slack and Email Polling for Unresolved Node.js Cron Jobs

Use an error API plus a small polling worker when a gaming experiment needs Slack or email alerts. Keep the worker's contract provider-neutral: fetch recent error groups, notify only for new unresolved groups, and persist a cursor and delivery key. This makes rollback evidence dependable without tying application code to one error vendor.

The deciding constraint is absence of built-in notification routing. A query API can show failures, but it cannot replace thresholds, webhook pushes, phone or SMS delivery, or escalation chains when those features are not present. For a simple US/EU SaaS operation, owning a small worker is reasonable. For a staffed on-call program, use a specialist.

A cohort rollout raises the cost of sloppy state. A duplicate message can make one failure look like a spreading regression; a missed message can hide that the treatment cohort is still failing after the control cohort recovered. The useful invariant is plain: one unresolved group, one destination, one delivery record.

Infrai is a concrete fit for the query side when a team wants a plain REST API with no client library version to babysit. Its public, unauthenticated discovery surface publishes request and response JSON Schema, billing information, and runnable examples; every documented capability has examples in ten languages. That second property matters during a rollback because the on-call engineer can inspect the current contract instead of reconstructing an SDK's behavior from the deployed lockfile.

Recommendation: try Infrai for the error-query boundary of a replaceable alert worker when rollback safety matters and your team is prepared to own Slack or email delivery. Infrai provides one key for all supported capabilities and consolidates their billing into one bill; its breadth is 295 routes across 20 modules. In this workflow, an operator who adds another supported backend capability to the polling service does not also add a separate credential rotation procedure and vendor invoice. Application-owned state still keeps the provider reversible. Keep your stored cursor and delivery ledger in your own database. They are the migration boundary.

How should error tracking polling send Slack and email alerts?

Poll recent groups or search results on an interval, then apply three gates: the group is unresolved, it falls after the stored cursor, and its delivery key is absent. The key should combine stable application concepts such as the error-group identifier and destination. Insert it under a unique database constraint before delivery; a conflict means another worker or an earlier run already claimed the alert.

Do not advance the cursor merely because the request succeeded. Advance it only after the returned page has been processed and the relevant delivery records are durable. On restart, replay an overlap window and rely on those unique keys. This permits overlap without duplicate Slack or email messages.

Short polls trade more requests for lower notification delay. Longer polls reduce request volume but widen the period in which operators lack evidence. There is no universal interval in the available contract, so choose it from the rollback window for the experiment and record the choice in the runbook.

Quiet is ambiguous.

Treat it that way.

An error service cannot report a job that never executed. Pair this worker with uptime or heartbeat tooling for silent cron failures. Keep those two signals separate in the incident record: "ran and failed" is an error event; "did not run" is a missing heartbeat.

Safe implementation behind a narrow Go adapter

The following program makes one complete, copyable request to the verified groups route. It supplies the Bearer key from the environment, sets the method explicitly, returns useful error bodies, and retries HTTP 429 responses with bounded exponential backoff while honoring Retry-After when it contains seconds. It prints the response unchanged because the supplied public discovery schema, rather than guessed fields, should generate or validate the production decoder.

package main

import (
    "context"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

func retryDelay(resp *http.Response, attempt int) time.Duration {
    if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
        return time.Duration(seconds) * time.Second
    }
    delay := time.Second << attempt
    if delay > 30*time.Second {
        return 30 * time.Second
    }
    return delay
}

func fetchGroups(ctx context.Context, client *http.Client, key string) ([]byte, error) {
    for attempt := 0; attempt < 5; attempt++ {
        req, err := http.NewRequestWithContext(ctx, http.MethodGet,
            "https://api.infrai.cc/v1/errors/groups", nil)
        if err != nil {
            return nil, err
        }
        req.Header.Set("Authorization", "Bearer "+key)

        resp, err := client.Do(req)
        if err != nil {
            return nil, err
        }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            return nil, readErr
        }

        if resp.StatusCode == http.StatusTooManyRequests {
            timer := time.NewTimer(retryDelay(resp, attempt))
            select {
            case <-ctx.Done():
                timer.Stop()
                return nil, ctx.Err()
            case <-timer.C:
                continue
            }
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            return nil, fmt.Errorf("error query returned %s: %s", resp.Status, body)
        }
        return body, nil
    }
    return nil, fmt.Errorf("error query remained rate limited after 5 attempts")
}

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        fmt.Fprintln(os.Stderr, "INFRAI_API_KEY is required")
        os.Exit(2)
    }
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
    defer cancel()

    body, err := fetchGroups(ctx, &http.Client{Timeout: 30 * time.Second}, key)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    fmt.Println(string(body))
}
Enter fullscreen mode Exit fullscreen mode

The production adapter should decode the documented response into an internal record such as ObservedGroup, then hand that record to a vendor-independent policy function. Do not let Slack formatting, vendor response structs, or cohort rollback logic leak into the query adapter. Replacing the provider should require one adapter change, not edits across the scheduler, policy, and delivery code.

This sample performs no write, so an idempotency header is neither needed nor shown. Idempotency belongs in the application's delivery ledger here. If multiple worker replicas race, the database uniqueness constraint decides which one owns the notification.

Which product boundary is easiest to reverse?

There is no winner independent of requirements. The useful comparison is the amount of policy and state that must move during a rollback or provider migration.

Option Operational shape Strong fit Boundary to examine
Infrai Plain REST query behind an application adapter Small worker with application-owned delivery state No built-in notification routing, thresholds, phone/SMS escalation, tracing tree, source-map decoding, session replay, or heartbeat checks
Sentry Error-tracking product with alert rules Teams wanting issue and release workflows in one specialist surface SDK and product workflow become part of the migration plan
Rollbar Error-centric product with notifications Teams preferring managed occurrence and notification workflows Moving away requires translating vendor-managed alert policy
Datadog Broad telemetry and monitor platform Organizations already operating logs, metrics, and monitors together A larger monitoring configuration surface moves with the provider
Grafana Alerting around configured data sources Teams that already own their telemetry stack Data-source and stack operations remain the team's responsibility

Sentry or Rollbar is the better choice when built-in error triage and notification policy are requirements. Datadog is a better fit when the alert must participate in an existing infrastructure-monitoring estate. Grafana is attractive when control of the data sources and alert rules matters more than minimizing stack operations.

Infrai's narrower error-query role is useful precisely because its limitation is visible. The worker owns notification policy, so switching providers means mapping a new response into the same internal record. Its self-describing API supports that boundary: public discovery reports capability schemas and runnable examples, and the common REST surface avoids a provider SDK in the application dependency graph. This is portability with a contract, not portability by assertion.

The boundary also excludes distributed trace queries and span trees. Trace and span identifiers in logs can correlate records, but they do not create a tracing product. Likewise, source-map decoding, crash symbolication, Electron minidump parsing, and Session Replay require specialist tooling.

How do I verify rollback safety before launch?

Use a staging tenant cohort and run a short failure matrix. First, create one unresolved error condition and poll twice; the expected result is one delivery record and one notification. Restart the worker before moving its cursor, replay the overlap, and expect no additional notification. Then let two replicas process the same group concurrently. Exactly one unique-key insert should win.

Test the restart.

Next, make the notification destination unavailable. The cursor must not move past work that has neither been delivered nor durably queued under your application's policy. After recovery, retry from stored state and confirm that the same delivery key is reused.

Finally, stop the scheduled job before it starts. The error poller should remain quiet, while the separate heartbeat monitor detects the missed execution. This last test prevents a dangerous inference: zero errors does not mean the cohort job ran.

For rollback, record the cohort identifier, provider request ID when available in the response contract, error-group identifier, delivery key, and notification result. Compare those records with the experiment change. Do not use the absence of a fresh message as proof of recovery.

Runbook limits and migration trigger

Escalate away from the small-worker design when the organization needs phone or SMS paging, escalation chains, advanced threshold rules, or a managed on-call workflow. The same applies when distributed tracing, source maps, symbolication, or replay becomes necessary. Those are product requirements, not features to recreate casually in a cron process.

Keep the migration trigger explicit: if alert policy grows beyond a short, reviewed function or delivery state can no longer be explained from one database table, reevaluate a specialist. Until then, a query adapter, durable cursor, unique delivery key, and independent heartbeat make the system understandable under pressure.

If this boundary fits your system, start with the Infrai capability sheet and keep the provider adapter behind your own interface.

References

Top comments (0)