diff --git a/examples/delete-contact-by-email.ts b/examples/delete-contact-by-email.ts new file mode 100644 index 0000000..3c435f5 --- /dev/null +++ b/examples/delete-contact-by-email.ts @@ -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(); diff --git a/src/__tests__/contacts.test.ts b/src/__tests__/contacts.test.ts new file mode 100644 index 0000000..b0b1e59 --- /dev/null +++ b/src/__tests__/contacts.test.ts @@ -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', + }) + ); + }); + }); +}); diff --git a/src/api/contacts.ts b/src/api/contacts.ts index ea5624b..9aa9b6d 100644 --- a/src/api/contacts.ts +++ b/src/api/contacts.ts @@ -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 @@ -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! * }); * ``` @@ -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, @@ -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