Skip to content

Contacts api #64

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 65 commits into from
Jun 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
7431edd
docs: update README to include Contacts API examples
narekhovhannisyan May 10, 2025
55aaa1e
examples: add examples for listing contacts and contact lists using M…
narekhovhannisyan May 10, 2025
0ae5397
feat: add getter for Contacts API in MailtrapClient
narekhovhannisyan May 10, 2025
ebc3855
feat: implement Contacts API class for managing contacts and contact …
narekhovhannisyan May 10, 2025
497f1c5
feat: add ContactLists API class for managing contact lists
narekhovhannisyan May 10, 2025
6598f3a
Merge branch 'batch-sending-api' of github.com:railsware/mailtrap-nod…
narekhovhannisyan May 17, 2025
8b86d36
examples: add examples for creating, deleting, and updating contacts …
narekhovhannisyan May 17, 2025
2e5af4d
types: add ContactFields and ContactData interfaces for managing cont…
narekhovhannisyan May 17, 2025
c7a75f3
refactor: update create and update methods in ContactsApi to use Cont…
narekhovhannisyan May 17, 2025
62032eb
revert: remove deprecated example files for listing contacts and cont…
narekhovhannisyan May 17, 2025
71c477f
examples: remove outdated contact example files and consolidate funct…
narekhovhannisyan May 17, 2025
81d6ef9
docs: update Contacts API examples to consolidate and improve clarity
narekhovhannisyan May 17, 2025
5ca3119
examples: add example for listing contact lists using MailtrapClient
narekhovhannisyan May 17, 2025
f992507
test: add comprehensive tests for contacts and contact lists API in M…
narekhovhannisyan May 17, 2025
4a7d388
feat: add getter for Contact Lists API in MailtrapClient
narekhovhannisyan May 17, 2025
63f4b79
test: refine update contact test to include email field in payload
narekhovhannisyan May 17, 2025
91b0cde
test: format update contact test for improved readability
narekhovhannisyan May 17, 2025
9f0a69a
docs: fix typo in Contacts API example file path in README
narekhovhannisyan May 17, 2025
e971cc8
fix: update ContactListsApi initialization to include accountId for p…
narekhovhannisyan May 17, 2025
7f75f82
types: update custom_variables and template_variables types to accept…
narekhovhannisyan May 17, 2025
47b77d4
fix: correct method call for deleting a contact in the contacts example
narekhovhannisyan May 17, 2025
25619ca
Merge branch 'main' of github.com:railsware/mailtrap-nodejs into cont…
narekhovhannisyan May 30, 2025
38ee629
feat: add ContactListsApi and ContactsApi classes for managing contac…
narekhovhannisyan May 31, 2025
ac9323b
refactor: rename ContactsApi and ContactListsApi imports for consistency
narekhovhannisyan May 31, 2025
398c486
refactor: update MailtrapClient to use ContactsBaseAPI and remove Con…
narekhovhannisyan May 31, 2025
891fff6
test: add unit tests for ContactListsApi and ContactsApi classes
narekhovhannisyan May 31, 2025
535b25b
test: add unit tests for Contacts class initialization
narekhovhannisyan May 31, 2025
72a7bd4
test: remove deprecated contact-related tests and update imports in m…
narekhovhannisyan May 31, 2025
ab67329
fix: update contact list retrieval to use the correct client structure
narekhovhannisyan May 31, 2025
c0d6116
docs: update README to include Contacts API section and correct links
narekhovhannisyan May 31, 2025
608fb4f
fix: ensure newline at end of file in everything.ts
narekhovhannisyan May 31, 2025
df7e902
feat: add Contact interface and related types for managing contacts i…
narekhovhannisyan May 31, 2025
e972d48
feat: add ContactList and ContactListData interfaces for API contact …
narekhovhannisyan May 31, 2025
b168aa7
resources: update ContactsApi to use new ContactUpdateData type and i…
narekhovhannisyan May 31, 2025
32f5cbd
resources: enhance ContactListsApi to return typed contact lists for …
narekhovhannisyan May 31, 2025
317e038
fix: update logging for contact lists retrieval to display full respo…
narekhovhannisyan May 31, 2025
d0a2a1e
test: update error handling test for ContactLists API to check for 40…
narekhovhannisyan May 31, 2025
de5cb1c
fix: change Contact id type from number to string for better compatib…
narekhovhannisyan May 31, 2025
fb3d521
types: update Contact interface to include status and change timestam…
narekhovhannisyan May 31, 2025
54365d2
resources: update ContactsApi to improve API methods for creating, up…
narekhovhannisyan May 31, 2025
e58c703
refactor: remove unused ContactListsApi class to streamline codebase
narekhovhannisyan May 31, 2025
ad40640
api: simplify ContactsBaseAPI by directly exposing ContactsApi method…
narekhovhannisyan May 31, 2025
4344a16
test: update Contacts tests to use string contactId and add list() me…
narekhovhannisyan May 31, 2025
7479575
test: remove ContactList tests as the ContactListsApi class has been …
narekhovhannisyan May 31, 2025
1dedd74
test: update Contacts tests to verify presence of create, update, del…
narekhovhannisyan May 31, 2025
a155271
examples: remove deprecated contact-lists.ts file to clean up the cod…
narekhovhannisyan May 31, 2025
36dc156
types: refactor Contact interface to export it and reorder properties…
narekhovhannisyan May 31, 2025
fb67805
resources: rename parameter in update method of ContactsApi for clarity
narekhovhannisyan May 31, 2025
1bf1854
api: bind ContactsApi methods in ContactsBaseAPI to maintain context …
narekhovhannisyan May 31, 2025
5c3e4ca
test: update Contacts test data for consistency and accuracy in updat…
narekhovhannisyan May 31, 2025
d697894
examples: enhance contact management by adding list functionality and…
narekhovhannisyan May 31, 2025
44aa5e2
doc: update Contacts reference to everything.ts for improved clarity
narekhovhannisyan Jun 5, 2025
18eef15
examples: remove contact listing code from everything.ts
narekhovhannisyan Jun 5, 2025
3d18a03
api: remove list method binding from ContactsBaseAPI to streamline fu…
narekhovhannisyan Jun 5, 2025
e2eac12
resources: remove contactListsURL and list method from ContactsApi
narekhovhannisyan Jun 5, 2025
8d01d01
test: change testInboxId to accountId in Contacts test suite
narekhovhannisyan Jun 5, 2025
2c9e1c4
types: update ContactFields and ContactData interfaces to allow field…
narekhovhannisyan Jun 5, 2025
86003d2
types: remove first_name and last_name fields from ContactFields inte…
narekhovhannisyan Jun 5, 2025
5a4c562
test: remove list() tests from Contacts test suite following API changes
narekhovhannisyan Jun 5, 2025
ca246cd
resources: add get method to ContactsApi for retrieving a contact by …
narekhovhannisyan Jun 7, 2025
970fe76
examples: update everything.ts to retrieve a contact by email before …
narekhovhannisyan Jun 7, 2025
3532edb
test: add get method assertion to Contacts test suite
narekhovhannisyan Jun 7, 2025
a07ebbf
test: enhance Contacts test suite with get() method assertions for su…
narekhovhannisyan Jun 7, 2025
272964f
api: bind get method in ContactsBaseAPI to enhance functionality
narekhovhannisyan Jun 7, 2025
26f43e4
examples: refactor everything.ts to create a contact before retrievin…
narekhovhannisyan Jun 7, 2025
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ Refer to the [`examples`](examples) folder for the source code of this and other
- [Remove account access](examples/general/accounts.ts)
- [Permissions](examples/general/permissions.ts)

### Contacts API

- [Contacts](examples/contacts/everything.ts)

### Sending API

- [Advanced](examples/sending/everything.ts)
Expand Down
47 changes: 47 additions & 0 deletions examples/contacts/everything.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { MailtrapClient } from "mailtrap";

const TOKEN = "<YOUR-TOKEN-HERE>";
const ACCOUNT_ID = "<YOUR-ACCOUNT-ID-HERE>"

const client = new MailtrapClient({
token: TOKEN,
accountId: ACCOUNT_ID
});

const contactData = {
email: "[email protected]",
fields: {
first_name: "John",
last_name: "Smith"
},
};

// Create contact first
client.contacts
.create(contactData)
.then(async (createResponse) => {
console.log("Contact created:", createResponse.data);
const contactId = createResponse.data.id;

// Get contact by email
const getResponse = await client.contacts.get(contactData.email);
console.log("Contact retrieved:", getResponse.data);

// Update contact
const updateResponse = await client.contacts
.update(contactId, {
email: contactData.email,
fields: {
first_name: "Johnny",
last_name: "Smith",
}
})
console.log("Contact updated:", updateResponse.data);

// Delete contact
await client.contacts.delete(contactId);
console.log("Contact deleted");
})
.catch(error => {
console.error("Error in contact lifecycle:", error);
});
19 changes: 19 additions & 0 deletions src/__tests__/lib/api/Contacts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import axios from "axios";

import Contacts from "../../../lib/api/Contacts";

describe("lib/api/Contacts: ", () => {
const accountId = 100;
const contactsAPI = new Contacts(axios, accountId);

describe("class Contacts(): ", () => {
describe("init: ", () => {
it("initalizes with all necessary params.", () => {
expect(contactsAPI).toHaveProperty("create");
expect(contactsAPI).toHaveProperty("get");
expect(contactsAPI).toHaveProperty("update");
expect(contactsAPI).toHaveProperty("delete");
});
});
});
});
269 changes: 269 additions & 0 deletions src/__tests__/lib/api/resources/Contacts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import axios from "axios";
import AxiosMockAdapter from "axios-mock-adapter";

import ContactsApi from "../../../../lib/api/resources/Contacts";
import handleSendingError from "../../../../lib/axios-logger";
import MailtrapError from "../../../../lib/MailtrapError";

import CONFIG from "../../../../config";

const { CLIENT_SETTINGS } = CONFIG;
const { GENERAL_ENDPOINT } = CLIENT_SETTINGS;

describe("lib/api/resources/Contacts: ", () => {
let mock: AxiosMockAdapter;
const accountId = 100;
const contactsAPI = new ContactsApi(axios, accountId);

const createContactRequest = {
contact: {
email: "[email protected]",
fields: {
first_name: "John",
last_name: "Smith",
},
list_ids: [1, 2, 3],
},
};

const createContactResponse = {
data: {
id: "018dd5e3-f6d2-7c00-8f9b-e5c3f2d8a132",
status: "subscribed",
email: "[email protected]",
fields: {
first_name: "John",
last_name: "Smith",
},
list_ids: [1, 2, 3],
created_at: 1742820600230,
updated_at: 1742820600230,
},
};

const updateContactRequest = {
contact: {
email: "[email protected]",
fields: {
first_name: "John",
last_name: "Smith",
zip_code: "11111",
},
list_ids_included: [1, 2, 3],
list_ids_excluded: [4, 5, 6],
unsubscribed: false,
},
};

const updateContactResponse = {
data: {
id: "01972696-84ef-783b-8a87-48067db2d16b",
email: "[email protected]",
created_at: 1748699088076,
updated_at: 1748700400794,
list_ids: [],
status: "subscribed",
fields: {
first_name: "Johnny",
last_name: "Smith",
},
},
};

describe("class ContactsApi(): ", () => {
describe("init: ", () => {
it("initializes with all necessary params.", () => {
expect(contactsAPI).toHaveProperty("create");
expect(contactsAPI).toHaveProperty("update");
expect(contactsAPI).toHaveProperty("delete");
});
});
});

beforeAll(() => {
/**
* Init Axios interceptors for handling response.data, errors.
*/
axios.interceptors.response.use(
(response) => response.data,
handleSendingError
);
mock = new AxiosMockAdapter(axios);
});

afterEach(() => {
mock.reset();
});

describe("get(): ", () => {
it("successfully gets a contact by email.", async () => {
const email = "[email protected]";
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts/${email}`;
const expectedResponseData = {
data: {
id: "018dd5e3-f6d2-7c00-8f9b-e5c3f2d8a132",
email: "[email protected]",
created_at: 1748699088076,
updated_at: 1748699088076,
list_ids: [1, 2, 3],
status: "subscribed",
fields: {
first_name: "John",
last_name: "Smith",
zip_code: "11111",
},
},
};

expect.assertions(2);

mock.onGet(endpoint).reply(200, expectedResponseData);
const result = await contactsAPI.get(email);

expect(mock.history.get[0].url).toEqual(endpoint);
expect(result).toEqual(expectedResponseData);
});

it("fails with error when getting a contact.", async () => {
const email = "[email protected]";
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts/${email}`;
const expectedErrorMessage = "Contact not found";

expect.assertions(2);

mock.onGet(endpoint).reply(404, { error: expectedErrorMessage });

try {
await contactsAPI.get(email);
} catch (error) {
expect(error).toBeInstanceOf(MailtrapError);
if (error instanceof MailtrapError) {
expect(error.message).toEqual(expectedErrorMessage);
}
}
});
});

describe("create(): ", () => {
it("successfully creates a contact.", async () => {
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts`;
const expectedResponseData = createContactResponse;

expect.assertions(2);

mock
.onPost(endpoint, createContactRequest)
.reply(200, expectedResponseData);
const result = await contactsAPI.create(createContactRequest.contact);

expect(mock.history.post[0].url).toEqual(endpoint);
expect(result).toEqual(expectedResponseData);
});

it("fails with error.", async () => {
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts`;
const expectedErrorMessage = "Request failed with status code 400";

expect.assertions(2);

mock.onPost(endpoint).reply(400, { error: expectedErrorMessage });

try {
await contactsAPI.create(createContactRequest.contact);
} catch (error) {
expect(error).toBeInstanceOf(MailtrapError);
if (error instanceof MailtrapError) {
expect(error.message).toEqual(expectedErrorMessage);
}
}
});
});

describe("update(): ", () => {
const contactId = "018dd5e3-f6d2-7c00-8f9b-e5c3f2d8a132";

it("successfully updates a contact.", async () => {
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts/${contactId}`;
const expectedResponseData = updateContactResponse;

expect.assertions(2);

mock
.onPatch(endpoint, updateContactRequest)
.reply(200, expectedResponseData);
const result = await contactsAPI.update(
contactId,
updateContactRequest.contact
);

expect(mock.history.patch[0].url).toEqual(endpoint);
expect(result).toEqual(expectedResponseData);
});

it("fails with error.", async () => {
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts/${contactId}`;
const expectedErrorMessage = "Request failed with status code 404";

expect.assertions(2);

mock.onPatch(endpoint).reply(404, { error: expectedErrorMessage });

try {
await contactsAPI.update(contactId, updateContactRequest.contact);
} catch (error) {
expect(error).toBeInstanceOf(MailtrapError);
if (error instanceof MailtrapError) {
expect(error.message).toEqual(expectedErrorMessage);
}
}
});
});

describe("delete(): ", () => {
const contactId = "018dd5e3-f6d2-7c00-8f9b-e5c3f2d8a132";

it("successfully deletes a contact.", async () => {
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts/${contactId}`;
const expectedResponseData = {
data: {
id: contactId,
status: "unsubscribed",
email: "[email protected]",
fields: {
first_name: "John",
last_name: "Smith",
},
list_ids: [],
created_at: 1740659901189,
updated_at: 1742903266889,
},
};

expect.assertions(2);

mock.onDelete(endpoint).reply(200, expectedResponseData);
const result = await contactsAPI.delete(contactId);

expect(mock.history.delete[0].url).toEqual(endpoint);
expect(result).toEqual(expectedResponseData);
});

it("fails with error.", async () => {
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts/${contactId}`;
const expectedErrorMessage = "Request failed with status code 404";

expect.assertions(2);

mock.onDelete(endpoint).reply(404, { error: expectedErrorMessage });

try {
await contactsAPI.delete(contactId);
} catch (error) {
expect(error).toBeInstanceOf(MailtrapError);
if (error instanceof MailtrapError) {
expect(error.message).toEqual(expectedErrorMessage);
}
}
});
});
});
2 changes: 1 addition & 1 deletion src/__tests__/lib/mailtrap-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { Mail, MailtrapClient } from "../..";
import MailtrapError from "../../lib/MailtrapError";

import CONFIG from "../../config";
import TestingAPI from "../../lib/api/Testing";
import GeneralAPI from "../../lib/api/General";
import TestingAPI from "../../lib/api/Testing";

const { ERRORS, CLIENT_SETTINGS } = CONFIG;
const { TESTING_ENDPOINT, BULK_ENDPOINT, SENDING_ENDPOINT } = CLIENT_SETTINGS;
Expand Down
10 changes: 9 additions & 1 deletion src/lib/MailtrapClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import axios, { AxiosInstance } from "axios";

import encodeMailBuffers from "./mail-buffer-encoder";
import handleSendingError from "./axios-logger";
import MailtrapError from "./MailtrapError";

import GeneralAPI from "./api/General";
import TestingAPI from "./api/Testing";
import ContactsBaseAPI from "./api/Contacts";

import CONFIG from "../config";

Expand All @@ -18,7 +20,6 @@ import {
BatchSendResponse,
BatchSendRequest,
} from "../types/mailtrap";
import MailtrapError from "./MailtrapError";

const { CLIENT_SETTINGS, ERRORS } = CONFIG;
const {
Expand Down Expand Up @@ -102,6 +103,13 @@ export default class MailtrapClient {
return new GeneralAPI(this.axios, this.accountId);
}

/**
* Getter for Contacts API.
*/
get contacts() {
return new ContactsBaseAPI(this.axios, this.accountId);
}

/**
* Returns configured host. Checks if `bulk` and `sandbox` modes are activated simultaneously,
* then reject with Mailtrap Error.
Expand Down
Loading