@codepup/simbly-node
v0.1.2
Published
Node.js SDK for Simbly - CodePup's AI-First Analytics Agent
Maintainers
Readme
Simbly SDK for Node.js
The official Node.js SDK for Simbly - AI-First Engagement Platform.
Track user behavior, identify users, and build data-driven engagement workflows with simple, production-ready APIs.
Features
- ✅ Simple API - Track events with one line of code
- ✅ Auto-batching - Efficient event buffering and batching (up to 500 events)
- ✅ Type-safe - Full TypeScript support with IntelliSense
- ✅ Automatic retries - Built-in exponential backoff for failed requests
- ✅ Schema auto-detection - Automatically infers event schemas
- ✅ Clerk integration - First-class support for Clerk authentication
- ✅ Production-ready - Automatic flush on process exit, error handling
Installation
npm install @codepup/simbly-nodeOr with other package managers:
# yarn
yarn add @codepup/simbly-node
# pnpm
pnpm add @codepup/simbly-node
# bun
bun add @codepup/simbly-nodeQuick Start
1. Initialize the SDK
import { createClient } from '@codepup/simbly-node'
const simbly = createClient({
apiKey: process.env.SIMBLY_API_KEY // Get your API key from dashboard
})2. Track Your First Event
await simbly.track('user.signed_up', {
user: {
userId: 'user_123',
email: '[email protected]'
},
properties: {
plan: 'pro',
source: 'landing_page'
}
})3. Identify Users
await simbly.identify({
user: {
userId: 'user_123',
email: '[email protected]'
},
traits: {
name: 'John Doe',
plan: 'pro',
company: 'Acme Corp'
}
})That's it! You're now tracking events in production.
Core Concepts
Events
Events represent actions that users take in your application. Examples:
user.signed_upsubscription.createdfeature.enabledpage.viewed
Users
Every event is associated with a user. You can identify users with:
userId- Your unique user ID (can be Clerk ID, internal ID, etc.)email- User's email address (optional)anonymousId- For anonymous users (before signup/login)
Properties
Properties are custom data attached to events. Examples:
- Plan type:
{ plan: 'pro' } - Button clicked:
{ button: 'upgrade' } - Page URL:
{ url: '/pricing' }
API Reference
createClient(config)
Creates a new Simbly client instance.
const simbly = createClient({
apiKey: 'simbly_workspace-id_secret',
// Optional configuration
debug: false, // Enable debug logging
sync: false, // Wait for events to send
maxRetries: 3, // Retry failed requests
timeout: 30000, // Request timeout (ms)
autoFlushOnExit: true, // Auto-flush on exit
flushInterval: 5000, // Buffer flush interval (ms)
// Error handler
onError: (error, events) => {
console.error('Simbly error:', error)
}
})Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| apiKey | string | Required | Your Simbly API key |
| debug | boolean | false | Enable debug logging |
| sync | boolean | false | Send events synchronously (waits for response) |
| maxRetries | number | 3 | Maximum retry attempts for failed requests |
| timeout | number | 30000 | Request timeout in milliseconds |
| autoFlushOnExit | boolean | true | Automatically flush events on process exit |
| flushInterval | number | 5000 | Time-based flush interval in milliseconds |
| onError | function | undefined | Callback for handling errors |
track(eventName, payload)
Track a user event.
await simbly.track('button.clicked', {
user: {
userId: 'user_123',
email: '[email protected]'
},
properties: {
button: 'upgrade',
location: 'navbar',
plan: 'free'
},
context: {
ip: '192.168.1.1',
userAgent: 'Mozilla/5.0...'
}
})Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| eventName | string | Yes | Name of the event (e.g., "user.signed_up") |
| payload.user | User | No* | User identification |
| payload.properties | object | No | Custom event properties |
| payload.context | object | No | Additional context |
| payload.idempotencyKey | string | No | Unique key for deduplication |
| payload.event_id | string | No | Custom event ID |
| payload.timestamp | string\|number | No | Custom timestamp |
*At least one user identifier is required (userId, email or anonymousId)
User Object
interface User {
userId?: string // Your unique user ID (Clerk ID, internal ID, etc.)
email?: string // User's email address
anonymousId?: string // Anonymous session ID (pre-auth)
traits?: object // Custom user traits
}Event Naming Conventions
Use descriptive, dot-separated names:
✅ Good:
user.signed_upsubscription.createdpage.viewed
❌ Avoid:
signup(too vague)user_signed_up(use dots, not underscores)USER.SIGNED.UP(use lowercase)
identify(payload)
Identify a user and update their profile.
await simbly.identify({
user: {
userId: 'user_123',
email: '[email protected]'
},
traits: {
name: 'John Doe',
plan: 'pro',
company: 'Acme Corp',
createdAt: '2024-01-15T00:00:00Z'
}
})When to Use Identify
- After user signs up
- When user updates profile
- When user upgrades/downgrades plan
- On login (to refresh user data)
alias(payload)
Link an anonymous user to a known identity.
// Step 1: Track with anonymous ID before signup
await simbly.track('page.viewed', {
user: { anonymousId: 'anon_abc123' },
properties: { page: 'pricing' }
})
// Step 2: User signs up - link identities
await simbly.alias({
previousId: 'anon_abc123',
userId: 'user_xyz789'
})
// Step 3: Identify with user data
await simbly.identify({
user: { userId: 'user_xyz789', email: '[email protected]' },
traits: { name: 'John Doe', plan: 'pro' }
})Recommended Flow
- Pre-login: Track with
anonymousId - On signup: Call
alias()to link identities - After signup: Call
identify()with user traits - Post-login: Track with
userId
aliasAndIdentify(previousId, payload)
Convenience method combining alias() and identify().
await simbly.aliasAndIdentify('anon_abc123', {
user: {
userId: 'user_xyz789',
email: '[email protected]'
},
traits: {
name: 'John Doe',
plan: 'pro'
}
})withContext(context)
Create a scoped client with default context applied to all events.
// Create scoped client
const scopedClient = simbly.withContext({
tenantId: 'acme',
environment: 'production'
})
// All events from this client include the context
await scopedClient.track('button.clicked', {
user: { userId: 'user_123' },
properties: { button: 'export' }
})
// Context automatically includes: { tenantId: 'acme', environment: 'production' }flush()
Immediately flush all buffered events.
await simbly.flush()When to Use Flush
- Before Lambda/serverless function exits
- Before long-running process checkpoint
- When you need to ensure events are sent immediately
shutdown()
Gracefully shutdown the client (flush and cleanup).
await simbly.shutdown()Usage Examples
Express.js API
import express from 'express'
import { createClient } from '@codepup/simbly-node'
const app = express()
const simbly = createClient({
apiKey: process.env.SIMBLY_API_KEY
})
app.post('/api/signup', async (req, res) => {
const { email, name } = req.body
// Create user in your database
const user = await db.users.create({ email, name })
// Track signup event
await simbly.track('user.signed_up', {
user: { userId: user.id, email: user.email },
properties: { plan: 'free', source: 'web' }
})
res.json({ success: true })
})
// Graceful shutdown
process.on('SIGTERM', async () => {
await simbly.shutdown()
process.exit(0)
})Next.js API Route
// app/api/track/route.ts
import { createClient } from '@codepup/simbly-node'
import { NextResponse } from 'next/server'
const simbly = createClient({
apiKey: process.env.SIMBLY_API_KEY!
})
export async function POST(request: Request) {
const { eventName, userId, properties } = await request.json()
await simbly.track(eventName, {
user: { userId },
properties
})
return NextResponse.json({ success: true })
}Clerk Integration
import { createClient } from '@codepup/simbly-node'
import { clerkClient } from '@clerk/nextjs/server'
const simbly = createClient({
apiKey: process.env.SIMBLY_API_KEY!
})
// After user signs up via Clerk
export async function POST(request: Request) {
const { userId } = await request.json()
// Get Clerk user data
const user = await clerkClient.users.getUser(userId)
// Track signup with Clerk data
await simbly.track('user.signed_up', {
user: {
userId: user.id, // Clerk user ID
email: user.emailAddresses[0].emailAddress
},
properties: {
firstName: user.firstName,
lastName: user.lastName
}
})
return Response.json({ success: true })
}AWS Lambda
import { createClient } from '@codepup/simbly-node'
const simbly = createClient({
apiKey: process.env.SIMBLY_API_KEY!,
sync: true // Wait for events to send
})
export const handler = async (event: any) => {
await simbly.track('lambda.invoked', {
user: { userId: event.userId },
properties: { functionName: process.env.AWS_LAMBDA_FUNCTION_NAME }
})
// Events are sent synchronously, safe to exit
return { statusCode: 200 }
}Background Job (Bull/BullMQ)
import { Queue, Worker } from 'bullmq'
import { createClient } from '@codepup/simbly-node'
const simbly = createClient({
apiKey: process.env.SIMBLY_API_KEY!
})
const worker = new Worker('email-queue', async (job) => {
await sendEmail(job.data.email)
// Track email sent
await simbly.track('email.sent', {
user: { email: job.data.email },
properties: {
templateId: job.data.templateId,
jobId: job.id
}
})
})
// Flush on shutdown
process.on('SIGTERM', async () => {
await simbly.shutdown()
await worker.close()
})Best Practices
1. Initialize Once
Create a single client instance and reuse it throughout your application.
// ✅ Good - singleton pattern
// lib/simbly.ts
export const simbly = createClient({
apiKey: process.env.SIMBLY_API_KEY!
})
// ❌ Bad - creates new client on every request
app.post('/track', (req, res) => {
const simbly = createClient({ apiKey: '...' }) // Don't do this!
})2. Use Async Mode (Default)
For better performance, use async mode (default) which batches events.
// ✅ Good - async mode (default)
const simbly = createClient({
apiKey: process.env.SIMBLY_API_KEY!
})
// ✅ Only use sync mode when necessary (Lambda, scripts)
const simbly = createClient({
apiKey: process.env.SIMBLY_API_KEY!,
sync: true // Events sent immediately
})3. Handle Errors Gracefully
Use the onError callback to handle failures without breaking your app.
const simbly = createClient({
apiKey: process.env.SIMBLY_API_KEY!,
onError: (error, events) => {
// Log to your error tracking service
console.error('Simbly tracking failed:', error)
// Don't throw - let app continue
}
})4. Use Descriptive Event Names
Follow a consistent naming pattern.
// ✅ Good - clear hierarchy
await simbly.track('user.signed_up', { ... })
await simbly.track('subscription.created', { ... })
await simbly.track('subscription.cancelled', { ... })
// ❌ Bad - inconsistent
await simbly.track('signup', { ... })
await simbly.track('new_subscription', { ... })
await simbly.track('cancel', { ... })5. Don't Track PII in Properties
The SDK automatically redacts sensitive fields, but avoid tracking PII when possible.
// ✅ Good - no PII in properties
await simbly.track('payment.completed', {
user: { userId: 'user_123' },
properties: {
amount: 49.99,
currency: 'USD',
plan: 'pro'
}
})
// ❌ Bad - PII in properties
await simbly.track('payment.completed', {
user: { userId: 'user_123' },
properties: {
creditCard: '4242-4242-4242-4242', // Will be redacted
password: 'secret123', // Will be redacted
ssn: '123-45-6789' // Will be redacted
}
})Automatically redacted fields:
password,passwd,pwdsecret,api_key,apikey,tokenauthorization,authssn,social_securitycredit_card,creditcard,cvv,ccv
6. Flush Before Exit
Always flush events before your process exits.
// Graceful shutdown
process.on('SIGTERM', async () => {
await simbly.flush()
process.exit(0)
})
process.on('SIGINT', async () => {
await simbly.flush()
process.exit(0)
})Or use shutdown() for complete cleanup:
process.on('SIGTERM', async () => {
await simbly.shutdown() // Flushes and cleans up
process.exit(0)
})Advanced Features
Custom Event IDs
Provide your own event IDs for deduplication.
await simbly.track('order.created', {
user: { userId: 'user_123' },
event_id: `order_${orderId}`, // Custom event ID
properties: { orderId, amount: 99.99 }
})Custom Timestamps
Track events with past or future timestamps.
await simbly.track('meeting.scheduled', {
user: { userId: 'user_123' },
timestamp: '2024-03-15T10:00:00Z', // ISO8601 format
properties: { meetingId: 'meet_123' }
})
// Or use epoch milliseconds
await simbly.track('meeting.scheduled', {
user: { userId: 'user_123' },
timestamp: 1710496800000, // Epoch ms
properties: { meetingId: 'meet_123' }
})Timestamp bounds:
- Cannot be more than 24 hours in the future
- Cannot be more than 90 days in the past
Schema Registration
Explicitly register event schemas for better validation and documentation.
await simbly.registerEvents([
{
name: 'user.signed_up',
description: 'User completed signup',
properties: {
email: { type: 'string', required: true },
plan: { type: 'string', enum: ['free', 'pro', 'enterprise'] },
source: { type: 'string' }
},
tags: ['user', 'conversion']
}
])Batch Processing
The SDK automatically batches events for efficiency:
- Default buffer size: 50 events
- Default flush interval: 5 seconds
- Maximum batch size: 500 events
- Batches >500 are automatically split
// All events are buffered and sent in batches
for (let i = 0; i < 1000; i++) {
await simbly.track('event.tracked', {
user: { userId: `user_${i}` },
properties: { index: i }
})
}
// Events are automatically sent in 2 batches of 500
await simbly.flush() // Send remaining buffered eventsError Handling
Handling Errors
const simbly = createClient({
apiKey: process.env.SIMBLY_API_KEY!,
onError: (error, events) => {
if (error.message.includes('401')) {
console.error('Invalid API key')
} else if (error.message.includes('429')) {
console.error('Rate limited')
} else {
console.error('Tracking error:', error)
}
}
})Common Errors
| Error | Cause | Solution |
|-------|-------|----------|
| Invalid API key | Wrong or expired API key | Check your API key in dashboard |
| Event name must be a non-empty string | Missing event name | Provide valid event name |
| Missing identity | No user identifier | Provide userId, email or anonymousId |
| Maximum 500 events per batch | Batch too large | SDK auto-splits, but check if manually sending |
TypeScript Support
The SDK is written in TypeScript and provides full type safety.
import { createClient, TrackPayload, User } from '@codepup/simbly-node'
const simbly = createClient({
apiKey: process.env.SIMBLY_API_KEY!
})
// Full IntelliSense support
const user: User = {
userId: 'user_123',
email: '[email protected]'
}
const payload: TrackPayload = {
user,
properties: {
plan: 'pro',
source: 'web'
}
}
await simbly.track('user.signed_up', payload)Runtime Support
The SDK works in multiple JavaScript runtimes:
- ✅ Node.js (v14+)
- ✅ Bun
- ✅ Deno
- ✅ AWS Lambda
- ✅ Cloudflare Workers (with modifications)
- ✅ Vercel Functions
Performance
Benchmarks
- Sync mode: ~5ms per event (network bound)
- Async mode: ~0.1ms per event (buffered)
- Memory: ~1KB per buffered event
- Network: Automatic batching reduces requests by 50x
Tips for High-Volume Applications
- Use async mode (default)
- Increase buffer size for high throughput
- Disable time-based flushing if needed
const simbly = createClient({
apiKey: process.env.SIMBLY_API_KEY!,
sync: false, // Async mode
flushInterval: 0 // Disable time-based flush (flush on buffer full only)
})FAQ
When should I call flush()?
- Serverless functions: Always flush before function exits
- Background jobs: Flush after job completes
- Long-running servers: Not needed (auto-flush every 5s)
- Scripts: Flush at the end
Should I use sync or async mode?
- Async (default): For web servers, APIs, long-running processes
- Sync: For Lambda, scripts, short-lived processes
How does batching work?
Events are automatically batched based on:
- Buffer size (50 events by default)
- Time interval (5 seconds by default)
- Process exit (auto-flush)
Can I track events from the browser?
No, this SDK is for server-side tracking only. For browser tracking, use our JavaScript SDK (coming soon).
How do I get an API key?
Contact us at [email protected] to get started with Simbly analytics for your application.
Troubleshooting
Events not appearing in dashboard
- ✅ Check API key is valid
- ✅ Ensure you called
flush()before exit - ✅ Check
onErrorcallback for errors - ✅ Enable debug mode:
{ debug: true }
High memory usage
- ✅ Reduce buffer size:
new EventBuffer(10) - ✅ Reduce flush interval:
{ flushInterval: 1000 } - ✅ Enable sync mode:
{ sync: true }
Events being dropped
- ✅ Check for validation errors in
onError - ✅ Ensure event names are valid
- ✅ Ensure at least one user identifier is provided
- ✅ Check properties are objects (not arrays)
Support
For any questions, API access, or support:
📧 Email: [email protected]
We're here to help you get started with Simbly analytics!
License
This SDK is provided for use with CodePup's Simbly analytics service. API access requires an active subscription.
Contact [email protected] for licensing and pricing information.
Changelog
v0.1.0 (Current)
- ✅ Initial release
- ✅ Track, identify, and alias APIs
- ✅ Auto-batching (up to 500 events)
- ✅ Automatic retries with exponential backoff
- ✅ TypeScript support
- ✅ Clerk integration
- ✅ Schema auto-detection
Ready to get started? Contact us at [email protected] to get your API key and start tracking events.
