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
46 changes: 46 additions & 0 deletions examples/delete-contact-by-email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Example: Delete a contact by email address
*
* This example demonstrates how to delete a contact from your SMASHSEND workspace
* using their email address instead of their contact ID.
*/

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

const smashsend = new SmashSend('your-api-key-here');

async function deleteContactByEmailExample() {
try {
// Delete a contact by email address
const result = await smashsend.contacts.deleteByEmail('user@example.com');

console.log('Contact deleted successfully:');
console.log('- Contact ID:', result.contact.id);
console.log('- Email:', result.contact.email);
console.log('- First Name:', result.contact.firstName);
console.log('- Last Name:', result.contact.lastName);
console.log('- Deleted:', result.isDeleted);

} catch (error) {
if (error.status === 404) {
console.error('Contact not found with that email address');
} else {
console.error('Failed to delete contact:', error.message);
}
}
}

// Handle emails with special characters
async function deleteContactWithSpecialCharacters() {
try {
// This will properly URL encode the email
const result = await smashsend.contacts.deleteByEmail('user+test@example.com');
console.log('Contact with special characters deleted:', result.contact.email);
} catch (error) {
console.error('Error:', error.message);
}
}

// Run the examples
deleteContactByEmailExample();
deleteContactWithSpecialCharacters();
99 changes: 99 additions & 0 deletions src/__tests__/contacts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { SmashSend } from '../index';
import fetch from 'cross-fetch';

// Mocking fetch
jest.mock('cross-fetch', () => {
return jest.fn(() =>
Promise.resolve({
ok: true,
status: 200,
json: () => Promise.resolve({
contact: {
id: 'contact-123',
email: 'test@example.com',
firstName: 'John',
lastName: 'Doe'
},
isDeleted: true
}),
text: () => Promise.resolve('Success'),
headers: {
get: jest.fn((name) => (name === 'content-type' ? 'application/json' : null)),
},
})
);
});

describe('SmashSend Contacts', () => {
let client: SmashSend;

beforeEach(() => {
jest.clearAllMocks();
client = new SmashSend('test-api-key');
});

describe('delete', () => {
it('should delete a contact by ID', async () => {
const result = await client.contacts.delete('contact-123');

expect(result).toEqual({
contact: {
id: 'contact-123',
email: 'test@example.com',
firstName: 'John',
lastName: 'Doe'
},
isDeleted: true
});

expect(fetch).toHaveBeenCalledWith(
expect.stringContaining('/v1/contacts/contact-123'),
expect.objectContaining({
method: 'DELETE',
headers: expect.objectContaining({
'Authorization': 'Bearer test-api-key',
'Content-Type': 'application/json',
}),
})
);
});
});

describe('deleteByEmail', () => {
it('should delete a contact by email address', async () => {
const result = await client.contacts.deleteByEmail('test@example.com');

expect(result).toEqual({
contact: {
id: 'contact-123',
email: 'test@example.com',
firstName: 'John',
lastName: 'Doe'
},
isDeleted: true
});

expect(fetch).toHaveBeenCalledWith(
expect.stringContaining('/v1/contacts/by-email/test%40example.com'),
expect.objectContaining({
method: 'DELETE',
headers: expect.objectContaining({
'Authorization': 'Bearer test-api-key',
'Content-Type': 'application/json',
}),
})
);
});

it('should properly encode email addresses with special characters', async () => {
await client.contacts.deleteByEmail('user+test@example.com');

expect(fetch).toHaveBeenCalledWith(
expect.stringContaining('/v1/contacts/by-email/user%2Btest%40example.com'),
expect.objectContaining({
method: 'DELETE',
})
);
});
});
});
23 changes: 17 additions & 6 deletions src/api/contacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class Contacts {

/**
* Create multiple contacts in a single batch operation
*
*
* @param contacts Array of contact creation options
* @param options Batch operation options
* @returns Batch operation result with success/failure details
Expand Down Expand Up @@ -71,13 +71,13 @@ export class Contacts {
*
* // ⚠️ MIGRATION USE ONLY - Preserve historical creation dates
* const legacyContacts = [
* {
* email: 'user@legacy-system.com',
* {
* email: 'user@legacy-system.com',
* firstName: 'John',
* createdAt: new Date('2023-01-15T10:30:00Z') // Historical date
* }
* ];
* await smashsend.contacts.createBatch(legacyContacts, {
* await smashsend.contacts.createBatch(legacyContacts, {
* overrideCreatedAt: true // Only for data migration!
* });
* ```
Expand Down Expand Up @@ -106,10 +106,10 @@ export class Contacts {
const payload = {
contacts: contacts.map((contact) => {
const { customProperties, createdAt, ...rest } = contact;

// Safely convert createdAt to ISO string
const createdAtIso = safeToISOString(createdAt);

return {
properties: {
...rest,
Expand Down Expand Up @@ -172,6 +172,17 @@ export class Contacts {
return this.httpClient.delete<{ isDeleted: boolean; contact: Contact }>(`/contacts/${id}`);
}

/**
* Delete a contact by email address
* @param email The contact email address
* @returns The deletion status and deleted contact
*/
async deleteByEmail(email: string): Promise<{ isDeleted: boolean; contact: Contact }> {
return this.httpClient.delete<{ isDeleted: boolean; contact: Contact }>(
`/contacts/by-email/${encodeURIComponent(email)}`
);
}

/**
* List contacts
* @param params Optional parameters for filtering and pagination
Expand Down