Skip to main content
npm install @tell-rs/node
A server-side SDK — you create a client, pass a user ID on every call, and a background worker handles batching and delivery over HTTP + JSONL. Zero dependencies. Data collected server-side is trustworthy and tamper-proof. Unlike the browser SDK, the Node.js SDK doesn’t auto-collect device context — you control exactly what properties are sent.

Quick start

import { Tell, production, Events } from "@tell-rs/node";

const tell = new Tell(production("a1b2c3d4e5f60718293a4b5c6d7e8f90"));

tell.track("user_123", "Page Viewed", { url: "/home" });
tell.identify("user_123", { name: "Jane", plan: "pro" });

await tell.close();

Verify it works

In development mode (logLevel: "debug"), the SDK logs to the console when events are batched and flushed. You can also call await tell.flush() and check that no error is reported via onError.

Configuration

Two presets are available:
// Production — default endpoint, batch 100, flush 10s, error-only logging
const tell = new Tell(production("YOUR_API_KEY"));

// Development — localhost:8080, batch 10, flush 2s, debug logging
const tell = new Tell(development("YOUR_API_KEY"));
For custom settings:
const tell = new Tell({
  apiKey: "YOUR_API_KEY",
  endpoint: "https://collect.internal",
  batchSize: 200,
  flushInterval: 5_000,
  maxRetries: 5,
  closeTimeout: 10_000,
  networkTimeout: 60_000,
  logLevel: "debug",
  gzip: true,
  onError: (e) => console.error("[Tell]", e),
});

Tracking events

Every method takes userId as its first parameter. For details on each event type and when to use them, see Events & Properties.
tell.track("user_123", "Feature Used", { feature: "dark_mode", enabled: true });

tell.identify("user_123", { name: "Jane", email: "jane@example.com" });

tell.group("user_123", "company_456", { plan: "enterprise" });

tell.revenue("user_123", 49.99, "USD", "order_789", { product: "annual_plan" });

tell.alias("anon_visitor_abc", "user_123");
Pass undefined when you don’t need properties:
tell.track("user_123", "App Started");

Standard event names

import { Events } from "@tell-rs/node";

tell.track("user_123", Events.PageViewed, { url: "/pricing" });
tell.track("user_123", Events.UserSignedUp, { source: "organic" });
tell.track("user_123", Events.OrderCompleted, { total: 99.00 });
tell.track("user_123", Events.FeatureUsed, { feature: "export" });
Constants cover user lifecycle, revenue, subscriptions, trials, shopping, engagement, and communication events. Custom string names always work too.

Super properties

tell.register({ app_version: "2.1.0", env: "production" });

// This track call automatically includes app_version and env
tell.track("user_123", "Click", { button: "submit" });

tell.unregister("env");
Event-specific properties override super properties when keys conflict. See Events & Properties for more on property patterns.

Structured logging

tell.logError("Payment failed", "billing", { error: "card_declined", amount: 9.99 });
tell.logInfo("User signed in", "auth", { method: "oauth" });
tell.logWarning("High memory usage", "api");
The service parameter defaults to "app". Nine severity levels from logEmergency to logTrace. See Logs for the full level reference and when to use each. Generic logging method:
tell.log("error", "Something broke", "worker", { job_id: "j_123" });

Redaction & beforeSend

Transform or drop events before they’re queued. Return null to drop an item. Supports a single function or an array of functions applied in order.

Drop health-check events

const tell = new Tell({
  apiKey: "YOUR_API_KEY",
  beforeSend: (event) => {
    if (event.properties?.url === "/health") return null;
    return event;
  },
});

Scrub user PII from traits

const tell = new Tell({
  apiKey: "YOUR_API_KEY",
  beforeSend: (event) => {
    if (event.traits?.email) {
      return { ...event, traits: { ...event.traits, email: "[REDACTED]" } };
    }
    return event;
  },
  beforeSendLog: [scrubPII, addTimezone], // pipeline of functions
});

redact() utility

For common patterns, use the built-in redact() factory:
import { Tell, redact, redactLog, SENSITIVE_PARAMS } from "@tell-rs/node";

const tell = new Tell({
  apiKey: "YOUR_API_KEY",
  beforeSend: redact({
    dropRoutes: ["/health", "/readyz"],
    stripParams: SENSITIVE_PARAMS,
    redactKeys: ["email", "phone", "ssn"],
  }),
  beforeSendLog: redactLog({
    redactKeys: ["password", "credit_card"],
  }),
});
You can combine redact() with custom hooks in an array:
const tell = new Tell({
  apiKey: "YOUR_API_KEY",
  beforeSend: [
    redact({ stripParams: SENSITIVE_PARAMS }),
    myCustomHook,
  ],
});
Server-side redaction prevents sensitive data from ever leaving your infrastructure. For defense in depth, also configure pipeline redaction to catch anything that slips through.

Lifecycle

await tell.flush();  // Force-send all queued events
tell.resetSession();  // Rotate session ID
await tell.close();   // Flush + shut down
Always call close() before your process exits to avoid losing buffered events.

Error handling

Tracking calls (track, identify, group, revenue, alias) and logging calls never throw. Validation errors and network failures route through the onError callback:
const tell = new Tell({
  apiKey: "YOUR_API_KEY",
  onError: (err) => console.error("[Tell]", err.message),
});
Constructor and lifecycle methods (flush, close) can throw or reject — handle these in your application code.

Retry behavior

On HTTP send failure (5xx or network error), the SDK retries with exponential backoff: 1-second base delay, 1.5x multiplier, 20% jitter, capped at 30 seconds. After maxRetries attempts (default: 3), the batch is dropped and reported via onError. 4xx errors (except 413) are not retried — they indicate a client-side issue (e.g. invalid API key). On 413 (payload too large), the SDK automatically halves the batch size for future sends.

Configuration reference

ParameterProductionDevelopmentDescription
endpointhttps://collect.tell.apphttp://localhost:8080HTTP collector URL
batchSize10010Events per batch before auto-flush
flushInterval10,000 ms2,000 msTime between auto-flushes
maxRetries33Retry attempts on send failure
closeTimeout5,000 ms5,000 msMax wait on close()
networkTimeout30,000 ms30,000 msHTTP request timeout
maxQueueSize1,0001,000Max queued items before oldest drops
gzipfalsefalseCompress payloads with gzip
sourceos.hostname()os.hostname()Log source identifier
logLevelerrordebugSDK log verbosity
disabledfalsefalseDrop all events locally