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()
...
});
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_name | firstName | first_name |
last_name | lastName | last_name |
email | email | email |
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.
- See Device ID Requirements for details.
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
});