Documentation

Events

Node.js SDK

@metricneed/node sends product events and insights from your backend — fire and forget, with retries and batching built in. Zero dependencies, Node 18+.

Install

npm install @metricneed/node

Create a key in Settings → Events API and keep it in your server's environment (METRICNEED_API_KEY). Keys are write-only, but they are still secrets: never ship one in browser code.

Track an event

import { MetricNeed } from '@metricneed/node'

const mn = new MetricNeed({ apiKey: process.env.METRICNEED_API_KEY })

mn.track({
  channel: 'signups',
  event: 'New signup',
  icon: 'user-plus',
  userId: 'usr_42',
  tags: { plan: 'pro', source: 'producthunt' }
})

track() never throws and never blocks your request: the event is sent in the background and failures go to onError. Fields mirror the REST API in camelCase (userId, idempotencyKey); icon is typed against the accepted icon list.

Await the result

const res = await mn.trackAsync({ channel: 'billing', event: 'Upgrade', value: 24 })
// res.accepted, res.duplicates, res.usage → { used, limit, resetsAt }

await mn.trackMany([
  { channel: 'deploys', event: 'Production deploy', pin: true },
  { channel: 'support', event: 'Ticket resolved', value: 42 }
])

trackAsync() and trackMany() resolve with the API's answer (including this month's usage) and throw a MetricNeedError on failure.

Batching

const mn = new MetricNeed({ apiKey, batch: true, flushInterval: 2000 })

mn.track({ channel: 'api', event: 'Request' }) // queued, sent every 2s

await mn.shutdown() // flush before a serverless function returns

In batch mode events are queued and sent every flushInterval, up to 100 per request. A batch that still fails after the retries is reported to onError and dropped, so a long outage can't grow memory without bound.

Insights

mn.insight.set('Waitlist', 1204, { icon: 'users' })
await mn.insight.setAsync('Open tickets', 3)

mn.insight.increment('Signups today')      // +1, atomic
mn.insight.increment('Open tickets', -1)   // any delta

Setting an insight again replaces its value; increment() adds to it atomically on the server (a new gauge starts at 0), so concurrent workers never lose an update. Numeric gauges keep a day-by-day history you can chart on a dashboard. Insights don't count against the monthly event quota.

Errors and retries

import { MetricNeed, MetricNeedError } from '@metricneed/node'

const mn = new MetricNeed({
  apiKey,
  onError: (err: MetricNeedError) => logger.warn('metricneed', err.code, err.message)
})

try {
  await mn.trackAsync({ channel: 'signups', event: 'New signup' })
} catch (err) {
  if (err instanceof MetricNeedError && err.code === 'event_quota') {
    // monthly quota reached — err.body.data has used / limit / resetsAt
  }
}

Network errors, timeouts, 408, 429 and 5xx are retried — honouring Retry-After, then with exponential backoff. 400 (invalid payload), 401 (bad key) and 402 (quota reached) are not: retrying wouldn't help. The API allows 600 requests per minute per key.

Options

OptionDefaultMeaning
apiKey—Required. The mne_… key from Settings → Events API.
baseUrlhttps://metricneed.comFor self-hosted or local instances.
batchfalseQueue track() calls and send them in batches.
flushInterval2000Batch interval in milliseconds.
maxBatchSize100Events per request (max 100). A full queue is sent right away.
maxRetries3Retries on network errors, timeouts, 408, 429 and 5xx.
timeout10000Per-attempt timeout in milliseconds.
onErrorwarn once per error kindReceives every error from fire-and-forget calls.
fetchglobalThis.fetchCustom fetch implementation.
flushOnExittrueBatch mode: send what is queued when the process is about to exit.

Frameworks

Next.js route handler:

// app/api/signup/route.ts
import { MetricNeed } from '@metricneed/node'

const mn = new MetricNeed({ apiKey: process.env.METRICNEED_API_KEY! })

export async function POST(req: Request) {
  const user = await createUser(await req.json())
  await mn.trackAsync({ channel: 'signups', event: 'New signup', userId: user.id })
  return Response.json(user)
}

Nuxt server route:

// server/api/signup.post.ts
import { MetricNeed } from '@metricneed/node'

const mn = new MetricNeed({ apiKey: useRuntimeConfig().metricneedApiKey })

export default defineEventHandler(async (event) => {
  const user = await createUser(await readBody(event))
  mn.track({ channel: 'signups', event: 'New signup', userId: user.id })
  return user
})

On serverless platforms, await trackAsync() — or call shutdown() in batch mode — before returning, so the function isn't frozen with the event still queued.

Without the SDK

curl -X POST https://metricneed.com/api/v1/events \
  -H "Authorization: Bearer mne_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channel": "signups", "event": "New signup", "icon": "user-plus", "user_id": "usr_42", "tags": { "plan": "pro" } }'

Any language that can send an HTTP request works — see the REST reference.