Skip to main content

Best Practices

Follow these best practices to get the most out of the Conviso Node SDK.

Configuration​

Use Environment Variables​

Never hardcode API keys:

// ✅ Good
const client = new CDPClient({
cdpApiKey: process.env.CDP_API_KEY
});

// ❌ Bad
const client = new CDPClient({
cdpApiKey: 'sk_live_abc123...' // Never do this!
});

Singleton Pattern​

Create a single client instance and reuse it:

// config/cdp.ts
import { CDPClient } from '@codematic.io/cdp-node';

export const cdpClient = new CDPClient({
cdpApiKey: process.env.CDP_API_KEY!,
maxConcurrentRequests: 20,
timeout: 10000,
debug: process.env.NODE_ENV === 'development'
});

// Use throughout your app
import { cdpClient } from './config/cdp';

Test Connection at Startup​

Verify configuration during application startup:

import { cdpClient } from './config/cdp';

async function startApp() {
try {
await cdpClient.ping();
console.log('Conviso connection verified ✓');
} catch (error) {
console.error('Conviso connection failed:', error);
process.exit(1);
}

// Start your server
app.listen(3000);
}

startApp();

User Identification​

Consistent Identifiers​

Always use the same identifier for a user across all Conviso operations. This is critical for ensuring data is correctly associated with user profiles.

// ✅ Good - Use consistent user ID everywhere
const userId = user.id;

// Step 1: Identify the user
await client.identify(userId, {
email: user.email,
firstName: user.firstName,
lastName: user.lastName
});

// Step 2: Track events with the same ID
await client.track(userId, 'purchase_completed', { amount: 99.99 });

// Step 3: Register device with the same ID
await client.registerDevice(userId, {
deviceId: 'device_123',
platform: 'ios',
fcmToken: 'token_here'
});

// Step 4: Send communications with the same ID
await client.sendEmail({
to: user.email,
identifiers: { id: userId },
transactional_message_id: 'WELCOME'
});

await client.sendPush({
identifiers: { id: userId },
transactional_message_id: 'NOTIFICATION'
});

// ❌ Bad - Switching between different identifier types
await client.identify(user.id, { email: user.email });
await client.track(user.email, 'purchase_completed', { ... }); // Different identifier!
await client.sendEmail({
to: user.email,
identifiers: { email: user.email }, // Different from identify()
...
});
Why This Matters

Using inconsistent identifiers can result in:

  • Duplicate user profiles
  • Events not being attributed to the correct user
  • Emails/push notifications failing to send
  • Incomplete user data

Person Attributes​

The Conviso automatically recognizes and sets the following top-level person attributes:

Property (snake_case)Property (camelCase)Person Attribute
first_namefirstNamefirst_name
last_namelastNamelast_name
emailemailemail

You can use either naming convention—the Conviso will automatically map them correctly.

// Both of these are equivalent:

// Using camelCase
await client.identify('user123', {
firstName: 'John',
lastName: 'Doe',
email: '[email protected]'
});

// Using snake_case
await client.identify('user123', {
first_name: 'John',
last_name: 'Doe',
email: '[email protected]'
});

Include Person Attributes Early​

Include email, first name, and last name on first identification:

// ✅ Good - Person attributes included immediately
await client.identify('user123', {
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
created_at: new Date().toISOString()
});

// ❌ Bad - Person attributes added later
await client.identify('user123', {
created_at: new Date().toISOString()
});
// Later...
await client.identify('user123', {
email: '[email protected]',
firstName: 'John',
lastName: 'Doe'
});

Person attributes are crucial for:

  • Email campaigns and personalization
  • User matching and deduplication
  • Segmentation and targeting
  • Better customer support

Use ISO 8601 for Dates​

Always use ISO 8601 format for dates:

// ✅ Good
await client.identify('user123', {
created_at: new Date().toISOString(), // "2025-10-29T10:30:00.000Z"
last_login: new Date().toISOString()
});

// ❌ Bad
await client.identify('user123', {
created_at: '10/29/2025', // Ambiguous format
last_login: Date.now() // Timestamp instead of ISO string
});

Error Handling​

Always Use Try-Catch​

Wrap Conviso calls in try-catch blocks:

// ✅ Good
try {
await client.identify('user123', { email: '[email protected]' });
} catch (error) {
logger.error('Failed to identify user', { error: error.message });
}

// ❌ Bad
await client.identify('user123', { email: '[email protected]' }); // Can crash app!

Fire and Forget for Non-Critical Operations​

For non-critical tracking, don't block:

// ✅ Good - Don't block page rendering
client.track('user123', 'page_viewed', { page: '/home' })
.catch(err => logger.error('Track failed', err));

res.render('home');

// ❌ Bad - Blocks page rendering
try {
await client.track('user123', 'page_viewed', { page: '/home' });
} catch (error) {
logger.error('Track failed', error);
}
res.render('home');

Implement Retry Logic​

Retry transient failures:

async function trackWithRetry(
userId: string,
event: string,
properties: Record<string, any>,
maxRetries = 3
) {
for (let i = 0; i < maxRetries; i++) {
try {
await client.track(userId, event, properties);
return;
} catch (error) {
if (i === maxRetries - 1) throw error;

// Exponential backoff
await sleep(Math.pow(2, i) * 1000);
}
}
}

Performance​

Adjust Concurrency for Your Workload​

// Low-traffic application
const client = new CDPClient({
cdpApiKey: process.env.CDP_API_KEY,
maxConcurrentRequests: 5
});

// High-traffic application
const client = new CDPClient({
cdpApiKey: process.env.CDP_API_KEY,
maxConcurrentRequests: 30 // Maximum
});

Security​

Never Log Sensitive Data​

// ✅ Good
logger.info('User identified', {
userId: 'user123',
email: 'u***@example.com' // Redacted
});

// ❌ Bad
logger.info('User identified', {
userId: 'user123',
email: '[email protected]',
password: 'secret123' // NEVER log passwords!
});

Don't Send PII Unnecessarily​

// ✅ Good - Only send necessary data
await client.identify('user123', {
email: '[email protected]',
name: 'John Doe',
plan: 'premium'
});

// ❌ Bad - Sending sensitive data
await client.identify('user123', {
email: '[email protected]',
name: 'John Doe',
ssn: '123-45-6789', // Don't send SSN!
credit_card: '1234...', // Don't send payment info!
password_hash: '...' // Don't send credentials!
});

Email Best Practices​

Validate Email Addresses​

function isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

async function sendWelcomeEmail(email: string, userId: string) {
if (!isValidEmail(email)) {
throw new Error('Invalid email address');
}

await client.sendEmail({
to: email,
identifiers: { id: userId },
transactional_message_id: 'WELCOME'
});
}

Push Notifications​

Register Devices Early​

Register devices as soon as possible:

// ✅ Good - Register on app launch
async function onAppLaunch() {
const userId = await getCurrentUserId();
const fcmToken = await getFCMToken();

if (userId && fcmToken) {
await client.registerDevice(userId, {
deviceId: getDeviceId(),
platform: 'ios',
fcmToken,
appVersion: getAppVersion()
});
}
}

Use Consistent Device IDs​

Critical: Each device must have a unique, persistent device ID that never changes.

Update Device Info Regularly​

// Update on each app launch
await client.registerDevice(userId, {
deviceId: deviceId,
platform: 'ios',
fcmToken: currentToken,
appVersion: '2.0.0', // Updated version
last_active_at: new Date().toISOString()
});

Keep Push Messages Concise​

// ✅ Good - Short and clear
await client.sendPush({
identifiers: { id: 'user123' },
transactional_message_id: 'ORDER_SHIPPED',
title: 'Your order has shipped!',
body: 'Track your package'
});

Testing​

Mock Conviso Client in Tests​

import { CDPClient } from '@codematic.io/cdp-node';

jest.mock('@codematic.io/cdp-node');

describe('UserService', () => {
test('identifies user on signup', async () => {
const mockIdentify = jest.fn().mockResolvedValue(undefined);
(CDPClient as jest.Mock).mockImplementation(() => ({
identify: mockIdentify
}));

await userService.signup('[email protected]', 'password');

expect(mockIdentify).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({ email: '[email protected]' })
);
});
});

Use Test Environment​

const client = new CDPClient({
cdpApiKey: process.env.NODE_ENV === 'test'
? 'test-key'
: process.env.CDP_API_KEY,
cdpEndpoint: process.env.NODE_ENV === 'test'
? 'https://test-api.example.com'
: undefined
});