Skip to content

Commit f97f15f

Browse files
Merge pull request #64 from railsware/contacts-api
Contacts api
2 parents 946ce71 + 26f43e4 commit f97f15f

File tree

11 files changed

+478
-2
lines changed

11 files changed

+478
-2
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ Refer to the [`examples`](examples) folder for the source code of this and other
5959
- [Remove account access](examples/general/accounts.ts)
6060
- [Permissions](examples/general/permissions.ts)
6161

62+
### Contacts API
63+
64+
- [Contacts](examples/contacts/everything.ts)
65+
6266
### Sending API
6367

6468
- [Advanced](examples/sending/everything.ts)

examples/contacts/everything.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { MailtrapClient } from "mailtrap";
2+
3+
const TOKEN = "<YOUR-TOKEN-HERE>";
4+
const ACCOUNT_ID = "<YOUR-ACCOUNT-ID-HERE>"
5+
6+
const client = new MailtrapClient({
7+
token: TOKEN,
8+
accountId: ACCOUNT_ID
9+
});
10+
11+
const contactData = {
12+
13+
fields: {
14+
first_name: "John",
15+
last_name: "Smith"
16+
},
17+
};
18+
19+
// Create contact first
20+
client.contacts
21+
.create(contactData)
22+
.then(async (createResponse) => {
23+
console.log("Contact created:", createResponse.data);
24+
const contactId = createResponse.data.id;
25+
26+
// Get contact by email
27+
const getResponse = await client.contacts.get(contactData.email);
28+
console.log("Contact retrieved:", getResponse.data);
29+
30+
// Update contact
31+
const updateResponse = await client.contacts
32+
.update(contactId, {
33+
email: contactData.email,
34+
fields: {
35+
first_name: "Johnny",
36+
last_name: "Smith",
37+
}
38+
})
39+
console.log("Contact updated:", updateResponse.data);
40+
41+
// Delete contact
42+
await client.contacts.delete(contactId);
43+
console.log("Contact deleted");
44+
})
45+
.catch(error => {
46+
console.error("Error in contact lifecycle:", error);
47+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import axios from "axios";
2+
3+
import Contacts from "../../../lib/api/Contacts";
4+
5+
describe("lib/api/Contacts: ", () => {
6+
const accountId = 100;
7+
const contactsAPI = new Contacts(axios, accountId);
8+
9+
describe("class Contacts(): ", () => {
10+
describe("init: ", () => {
11+
it("initalizes with all necessary params.", () => {
12+
expect(contactsAPI).toHaveProperty("create");
13+
expect(contactsAPI).toHaveProperty("get");
14+
expect(contactsAPI).toHaveProperty("update");
15+
expect(contactsAPI).toHaveProperty("delete");
16+
});
17+
});
18+
});
19+
});
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
import axios from "axios";
2+
import AxiosMockAdapter from "axios-mock-adapter";
3+
4+
import ContactsApi from "../../../../lib/api/resources/Contacts";
5+
import handleSendingError from "../../../../lib/axios-logger";
6+
import MailtrapError from "../../../../lib/MailtrapError";
7+
8+
import CONFIG from "../../../../config";
9+
10+
const { CLIENT_SETTINGS } = CONFIG;
11+
const { GENERAL_ENDPOINT } = CLIENT_SETTINGS;
12+
13+
describe("lib/api/resources/Contacts: ", () => {
14+
let mock: AxiosMockAdapter;
15+
const accountId = 100;
16+
const contactsAPI = new ContactsApi(axios, accountId);
17+
18+
const createContactRequest = {
19+
contact: {
20+
21+
fields: {
22+
first_name: "John",
23+
last_name: "Smith",
24+
},
25+
list_ids: [1, 2, 3],
26+
},
27+
};
28+
29+
const createContactResponse = {
30+
data: {
31+
id: "018dd5e3-f6d2-7c00-8f9b-e5c3f2d8a132",
32+
status: "subscribed",
33+
34+
fields: {
35+
first_name: "John",
36+
last_name: "Smith",
37+
},
38+
list_ids: [1, 2, 3],
39+
created_at: 1742820600230,
40+
updated_at: 1742820600230,
41+
},
42+
};
43+
44+
const updateContactRequest = {
45+
contact: {
46+
47+
fields: {
48+
first_name: "John",
49+
last_name: "Smith",
50+
zip_code: "11111",
51+
},
52+
list_ids_included: [1, 2, 3],
53+
list_ids_excluded: [4, 5, 6],
54+
unsubscribed: false,
55+
},
56+
};
57+
58+
const updateContactResponse = {
59+
data: {
60+
id: "01972696-84ef-783b-8a87-48067db2d16b",
61+
62+
created_at: 1748699088076,
63+
updated_at: 1748700400794,
64+
list_ids: [],
65+
status: "subscribed",
66+
fields: {
67+
first_name: "Johnny",
68+
last_name: "Smith",
69+
},
70+
},
71+
};
72+
73+
describe("class ContactsApi(): ", () => {
74+
describe("init: ", () => {
75+
it("initializes with all necessary params.", () => {
76+
expect(contactsAPI).toHaveProperty("create");
77+
expect(contactsAPI).toHaveProperty("update");
78+
expect(contactsAPI).toHaveProperty("delete");
79+
});
80+
});
81+
});
82+
83+
beforeAll(() => {
84+
/**
85+
* Init Axios interceptors for handling response.data, errors.
86+
*/
87+
axios.interceptors.response.use(
88+
(response) => response.data,
89+
handleSendingError
90+
);
91+
mock = new AxiosMockAdapter(axios);
92+
});
93+
94+
afterEach(() => {
95+
mock.reset();
96+
});
97+
98+
describe("get(): ", () => {
99+
it("successfully gets a contact by email.", async () => {
100+
const email = "[email protected]";
101+
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts/${email}`;
102+
const expectedResponseData = {
103+
data: {
104+
id: "018dd5e3-f6d2-7c00-8f9b-e5c3f2d8a132",
105+
106+
created_at: 1748699088076,
107+
updated_at: 1748699088076,
108+
list_ids: [1, 2, 3],
109+
status: "subscribed",
110+
fields: {
111+
first_name: "John",
112+
last_name: "Smith",
113+
zip_code: "11111",
114+
},
115+
},
116+
};
117+
118+
expect.assertions(2);
119+
120+
mock.onGet(endpoint).reply(200, expectedResponseData);
121+
const result = await contactsAPI.get(email);
122+
123+
expect(mock.history.get[0].url).toEqual(endpoint);
124+
expect(result).toEqual(expectedResponseData);
125+
});
126+
127+
it("fails with error when getting a contact.", async () => {
128+
const email = "[email protected]";
129+
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts/${email}`;
130+
const expectedErrorMessage = "Contact not found";
131+
132+
expect.assertions(2);
133+
134+
mock.onGet(endpoint).reply(404, { error: expectedErrorMessage });
135+
136+
try {
137+
await contactsAPI.get(email);
138+
} catch (error) {
139+
expect(error).toBeInstanceOf(MailtrapError);
140+
if (error instanceof MailtrapError) {
141+
expect(error.message).toEqual(expectedErrorMessage);
142+
}
143+
}
144+
});
145+
});
146+
147+
describe("create(): ", () => {
148+
it("successfully creates a contact.", async () => {
149+
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts`;
150+
const expectedResponseData = createContactResponse;
151+
152+
expect.assertions(2);
153+
154+
mock
155+
.onPost(endpoint, createContactRequest)
156+
.reply(200, expectedResponseData);
157+
const result = await contactsAPI.create(createContactRequest.contact);
158+
159+
expect(mock.history.post[0].url).toEqual(endpoint);
160+
expect(result).toEqual(expectedResponseData);
161+
});
162+
163+
it("fails with error.", async () => {
164+
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts`;
165+
const expectedErrorMessage = "Request failed with status code 400";
166+
167+
expect.assertions(2);
168+
169+
mock.onPost(endpoint).reply(400, { error: expectedErrorMessage });
170+
171+
try {
172+
await contactsAPI.create(createContactRequest.contact);
173+
} catch (error) {
174+
expect(error).toBeInstanceOf(MailtrapError);
175+
if (error instanceof MailtrapError) {
176+
expect(error.message).toEqual(expectedErrorMessage);
177+
}
178+
}
179+
});
180+
});
181+
182+
describe("update(): ", () => {
183+
const contactId = "018dd5e3-f6d2-7c00-8f9b-e5c3f2d8a132";
184+
185+
it("successfully updates a contact.", async () => {
186+
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts/${contactId}`;
187+
const expectedResponseData = updateContactResponse;
188+
189+
expect.assertions(2);
190+
191+
mock
192+
.onPatch(endpoint, updateContactRequest)
193+
.reply(200, expectedResponseData);
194+
const result = await contactsAPI.update(
195+
contactId,
196+
updateContactRequest.contact
197+
);
198+
199+
expect(mock.history.patch[0].url).toEqual(endpoint);
200+
expect(result).toEqual(expectedResponseData);
201+
});
202+
203+
it("fails with error.", async () => {
204+
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts/${contactId}`;
205+
const expectedErrorMessage = "Request failed with status code 404";
206+
207+
expect.assertions(2);
208+
209+
mock.onPatch(endpoint).reply(404, { error: expectedErrorMessage });
210+
211+
try {
212+
await contactsAPI.update(contactId, updateContactRequest.contact);
213+
} catch (error) {
214+
expect(error).toBeInstanceOf(MailtrapError);
215+
if (error instanceof MailtrapError) {
216+
expect(error.message).toEqual(expectedErrorMessage);
217+
}
218+
}
219+
});
220+
});
221+
222+
describe("delete(): ", () => {
223+
const contactId = "018dd5e3-f6d2-7c00-8f9b-e5c3f2d8a132";
224+
225+
it("successfully deletes a contact.", async () => {
226+
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts/${contactId}`;
227+
const expectedResponseData = {
228+
data: {
229+
id: contactId,
230+
status: "unsubscribed",
231+
232+
fields: {
233+
first_name: "John",
234+
last_name: "Smith",
235+
},
236+
list_ids: [],
237+
created_at: 1740659901189,
238+
updated_at: 1742903266889,
239+
},
240+
};
241+
242+
expect.assertions(2);
243+
244+
mock.onDelete(endpoint).reply(200, expectedResponseData);
245+
const result = await contactsAPI.delete(contactId);
246+
247+
expect(mock.history.delete[0].url).toEqual(endpoint);
248+
expect(result).toEqual(expectedResponseData);
249+
});
250+
251+
it("fails with error.", async () => {
252+
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/contacts/${contactId}`;
253+
const expectedErrorMessage = "Request failed with status code 404";
254+
255+
expect.assertions(2);
256+
257+
mock.onDelete(endpoint).reply(404, { error: expectedErrorMessage });
258+
259+
try {
260+
await contactsAPI.delete(contactId);
261+
} catch (error) {
262+
expect(error).toBeInstanceOf(MailtrapError);
263+
if (error instanceof MailtrapError) {
264+
expect(error.message).toEqual(expectedErrorMessage);
265+
}
266+
}
267+
});
268+
});
269+
});

src/__tests__/lib/mailtrap-client.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { Mail, MailtrapClient } from "../..";
77
import MailtrapError from "../../lib/MailtrapError";
88

99
import CONFIG from "../../config";
10-
import TestingAPI from "../../lib/api/Testing";
1110
import GeneralAPI from "../../lib/api/General";
11+
import TestingAPI from "../../lib/api/Testing";
1212

1313
const { ERRORS, CLIENT_SETTINGS } = CONFIG;
1414
const { TESTING_ENDPOINT, BULK_ENDPOINT, SENDING_ENDPOINT } = CLIENT_SETTINGS;

src/lib/MailtrapClient.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import axios, { AxiosInstance } from "axios";
55

66
import encodeMailBuffers from "./mail-buffer-encoder";
77
import handleSendingError from "./axios-logger";
8+
import MailtrapError from "./MailtrapError";
89

910
import GeneralAPI from "./api/General";
1011
import TestingAPI from "./api/Testing";
12+
import ContactsBaseAPI from "./api/Contacts";
1113

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

@@ -18,7 +20,6 @@ import {
1820
BatchSendResponse,
1921
BatchSendRequest,
2022
} from "../types/mailtrap";
21-
import MailtrapError from "./MailtrapError";
2223

2324
const { CLIENT_SETTINGS, ERRORS } = CONFIG;
2425
const {
@@ -102,6 +103,13 @@ export default class MailtrapClient {
102103
return new GeneralAPI(this.axios, this.accountId);
103104
}
104105

106+
/**
107+
* Getter for Contacts API.
108+
*/
109+
get contacts() {
110+
return new ContactsBaseAPI(this.axios, this.accountId);
111+
}
112+
105113
/**
106114
* Returns configured host. Checks if `bulk` and `sandbox` modes are activated simultaneously,
107115
* then reject with Mailtrap Error.

0 commit comments

Comments
 (0)