Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added comprehensive tests and example usage
- Integrates with new SMASHSEND backend endpoint `/v1/contacts/by-email/{email}`

- **Events API Improvements**: New clearer method names for better developer experience
- 🎯 **New methods**: `events.send()` and `events.sendBatch()` - much clearer than "track"!
- 📚 **Better documentation**: Updated examples and documentation to use the new method names
- 📝 **Added Events API section**: Comprehensive guide in README with practical examples
- 🔧 **New example file**: `examples/events-usage.ts` showing real-world usage patterns

## [1.16.0] - 2025-01-21

### Changed
Expand Down
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,68 @@ const property = await smashsend.contacts.createProperty({
- `STRING` for email addresses, URLs, phone numbers, and any text
- `NUMBER` for both integers and decimals

## Events API

Send events to trigger automations, track user behavior, or sync data with your SMASHSEND workspace.

**Send a single event:**

```typescript
const response = await smashsend.events.send({
event: 'user.signup',
identify: {
email: 'user@example.com',
traits: {
firstName: 'John',
lastName: 'Doe',
plan: 'premium'
}
},
properties: {
source: 'website',
campaign: 'summer-sale'
}
});

console.log(`Event sent with ID: ${response.messageId}`);
```

**Send multiple events in a batch:**

```typescript
const events = [
{
event: 'page.view',
identify: { email: 'user1@example.com' },
properties: { page: '/home' }
},
{
event: 'button.click',
identify: { email: 'user2@example.com' },
properties: { button: 'signup' }
}
];

const result = await smashsend.events.sendBatch(events);
console.log(`Accepted: ${result.accepted}, Failed: ${result.failed}`);

// Handle failed events
if (result.errors?.length > 0) {
result.errors.forEach(error => {
console.log(`Event ${error.index} failed:`, error.errors);
});
}
```

**Event structure:**

- `event`: Event name (e.g., 'user.signup', 'purchase.completed')
- `identify.email`: User email (required)
- `identify.traits`: User attributes to sync with contact record (optional)
- `properties`: Event-specific data (optional)
- `timestamp`: Event timestamp (optional, defaults to current time)
- `messageId`: Custom ID for deduplication (optional, auto-generated if not provided)

## TypeScript Support

- Built **in TypeScript**
Expand Down
151 changes: 151 additions & 0 deletions examples/events-usage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/**
* SMASHSEND Events API Examples
*
* This example demonstrates how to use the new events.send() and events.sendBatch() methods
* to track user events and trigger automations in your SMASHSEND workspace.
*/

import { SmashSend } from '@smashsend/node';

const smashsend = new SmashSend(process.env.SMASHSEND_API_KEY!);

async function sendSingleEvent() {
try {
const response = await smashsend.events.send({
event: 'user.signup',
identify: {
email: 'user@example.com',
traits: {
firstName: 'John',
lastName: 'Doe',
plan: 'premium',
signupSource: 'website'
}
},
properties: {
campaign: 'summer-sale',
referrer: 'google',
utm_source: 'social'
}
});

console.log(`✅ Event sent successfully with ID: ${response.messageId}`);
} catch (error) {
console.error('❌ Failed to send event:', error);
}
}

async function sendBatchEvents() {
const events = [
{
event: 'page.view',
identify: {
email: 'user1@example.com',
traits: { firstName: 'Alice' }
},
properties: {
page: '/pricing',
duration: 45000
}
},
{
event: 'button.click',
identify: {
email: 'user2@example.com',
traits: { firstName: 'Bob' }
},
properties: {
button: 'signup',
location: 'header'
}
},
{
event: 'purchase.completed',
identify: {
email: 'user3@example.com',
traits: { firstName: 'Carol' }
},
properties: {
orderId: 'order_123',
amount: 99.99,
currency: 'USD'
}
}
];

try {
const result = await smashsend.events.sendBatch(events);

console.log(`📊 Batch results:`);
console.log(` ✅ Accepted: ${result.accepted}`);
console.log(` ❌ Failed: ${result.failed}`);
console.log(` 🔄 Duplicated: ${result.duplicated}`);

// Handle failed events
if (result.errors?.length > 0) {
console.log('\n⚠️ Failed events:');
result.errors.forEach(error => {
console.log(` Event ${error.index}:`, error.errors.map(e => e.message).join(', '));
});
}

// Show successful events
if (result.events?.length > 0) {
console.log('\n✅ Successful events:');
result.events.forEach(event => {
console.log(` Index ${event.index}: ${event.messageId} (${event.status})`);
});
}
} catch (error) {
console.error('❌ Failed to send batch events:', error);
}
}

async function sendEventWithCustomDeduplication() {
try {
const response = await smashsend.events.send({
event: 'order.created',
identify: {
email: 'customer@example.com',
traits: {
customerId: 'cust_12345'
}
},
properties: {
orderId: 'order_67890',
amount: 149.99
},
// Custom messageId prevents duplicate processing if this exact event is sent again
messageId: 'order_67890_created'
});

console.log(`✅ Order event sent with ID: ${response.messageId}`);
} catch (error) {
console.error('❌ Failed to send order event:', error);
}
}

// Run examples
async function main() {
console.log('🚀 SMASHSEND Events API Examples\n');

console.log('1. Sending single event...');
await sendSingleEvent();

console.log('\n2. Sending batch events...');
await sendBatchEvents();

console.log('\n3. Sending event with custom deduplication...');
await sendEventWithCustomDeduplication();
}

// Only run if this file is executed directly
if (require.main === module) {
main().catch(console.error);
}

export {
sendSingleEvent,
sendBatchEvents,
sendEventWithCustomDeduplication
};
123 changes: 123 additions & 0 deletions src/__tests__/events.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { SmashSend } from '../index';
import { HttpClient } from '../utils/http-client';

// Mock the HttpClient
jest.mock('../utils/http-client');
const MockedHttpClient = HttpClient as jest.MockedClass<typeof HttpClient>;

describe('Events API', () => {
let smashsend: SmashSend;
let mockHttpClient: jest.Mocked<HttpClient>;

beforeEach(() => {
// Clear all mocks
jest.clearAllMocks();

// Create a new SmashSend instance
smashsend = new SmashSend('test-api-key');

// Get the mocked http client instance
mockHttpClient = MockedHttpClient.mock.instances[0] as jest.Mocked<HttpClient>;
});

describe('send() method', () => {
it('should send a single event successfully', async () => {
const mockResponse = {
success: true,
messageId: 'msg_123',
};

mockHttpClient.post.mockResolvedValue(mockResponse);

const eventPayload = {
event: 'user.signup',
identify: {
email: 'user@example.com',
traits: {
firstName: 'John',
lastName: 'Doe',
},
},
properties: {
plan: 'premium',
source: 'website',
},
};

const result = await smashsend.events.send(eventPayload);

expect(mockHttpClient.post).toHaveBeenCalledWith(
'/events',
eventPayload,
{ headers: {} }
);
expect(result).toEqual(mockResponse);
});

it('should send event with options', async () => {
const mockResponse = {
success: true,
messageId: 'msg_456',
};

mockHttpClient.post.mockResolvedValue(mockResponse);

const eventPayload = {
event: 'purchase.completed',
identify: { email: 'user@example.com' },
properties: { orderId: 'order_123' },
};

const options = {
headers: { 'X-Custom': 'header' },
timeout: 5000,
};

await smashsend.events.send(eventPayload, options);

expect(mockHttpClient.post).toHaveBeenCalledWith(
'/events',
eventPayload,
{ headers: { 'X-Custom': 'header' }, timeout: 5000 }
);
});
});

describe('sendBatch() method', () => {
it('should send multiple events successfully', async () => {
const mockResponse = {
accepted: 2,
failed: 0,
duplicated: 0,
events: [
{ index: 0, messageId: 'msg_123', status: 'accepted' as const },
{ index: 1, messageId: 'msg_456', status: 'accepted' as const },
],
};

mockHttpClient.post.mockResolvedValue(mockResponse);

const events = [
{
event: 'page.view',
identify: { email: 'user1@example.com' },
properties: { page: '/home' },
},
{
event: 'button.click',
identify: { email: 'user2@example.com' },
properties: { button: 'signup' },
},
];

const result = await smashsend.events.sendBatch(events);

expect(mockHttpClient.post).toHaveBeenCalledWith(
'/events/batch',
{ events },
{ headers: {} }
);
expect(result).toEqual(mockResponse);
});
});
});
Loading