-
Notifications
You must be signed in to change notification settings - Fork 11
Suppressions api #68
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
Suppressions api #68
Changes from 13 commits
66ef0e4
3171823
4c8580b
f0e3907
5f88ec2
4912a5b
f576198
43a42bd
1520fdb
8e47f33
13b8ad9
a2c3e6e
a8ec09e
bdc8e9e
88bff56
70541b5
8c28b49
9312aa2
3ba3365
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,30 @@ | ||||||
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 | ||||||
}); | ||||||
|
||||||
async function suppressionsFlow() { | ||||||
// Get all suppressions | ||||||
const allSuppressions = await client.suppressions.getList(); | ||||||
console.log("All suppressions:", allSuppressions); | ||||||
|
||||||
// Get suppressions filtered by email | ||||||
const filteredSuppressions = await client.suppressions.getList("test@example.com"); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd make this a named parameter, so that it's easier to add more named filters in the future.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @narekhovhannisyan this example should be updated after we changed the arguments |
||||||
console.log("Filtered suppressions:", filteredSuppressions); | ||||||
|
||||||
// Delete a suppression by ID (if any exist) | ||||||
if (allSuppressions.length > 0) { | ||||||
const suppressionToDelete = allSuppressions[0]; | ||||||
await client.suppressions.delete(suppressionToDelete.id); | ||||||
console.log(`Suppression ${suppressionToDelete.id} deleted successfully`); | ||||||
} else { | ||||||
console.log("No suppressions found to delete"); | ||||||
} | ||||||
} | ||||||
|
||||||
suppressionsFlow().catch(console.error); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,17 @@ | ||||||
import axios from "axios"; | ||||||
|
||||||
import SuppressionsBaseAPI from "../../../lib/api/Suppressions"; | ||||||
|
||||||
describe("lib/api/Suppressions: ", () => { | ||||||
const accountId = 100; | ||||||
const suppressionsAPI = new SuppressionsBaseAPI(axios, accountId); | ||||||
|
||||||
describe("class SuppressionsBaseAPI(): ", () => { | ||||||
describe("init: ", () => { | ||||||
it("initalizes with all necessary params.", () => { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A small typo:
Suggested change
|
||||||
expect(suppressionsAPI).toHaveProperty("getList"); | ||||||
expect(suppressionsAPI).toHaveProperty("delete"); | ||||||
}); | ||||||
}); | ||||||
}); | ||||||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import axios from "axios"; | ||
import AxiosMockAdapter from "axios-mock-adapter"; | ||
|
||
import SuppressionsApi from "../../../../lib/api/resources/Suppressions"; | ||
import handleSendingError from "../../../../lib/axios-logger"; | ||
import MailtrapError from "../../../../lib/MailtrapError"; | ||
import { Suppression } from "../../../../types/api/suppressions"; | ||
|
||
import CONFIG from "../../../../config"; | ||
|
||
const { CLIENT_SETTINGS } = CONFIG; | ||
const { GENERAL_ENDPOINT } = CLIENT_SETTINGS; | ||
|
||
describe("lib/api/resources/Suppressions: ", () => { | ||
let mock: AxiosMockAdapter; | ||
const accountId = 100; | ||
const suppressionsAPI = new SuppressionsApi(axios, accountId); | ||
|
||
const mockSuppression: Suppression = { | ||
id: "1", | ||
email: "test@example.com", | ||
type: "hard bounce", | ||
created_at: "2023-01-01T00:00:00Z", | ||
sending_stream: "transactional", | ||
domain_name: "example.com", | ||
message_bounce_category: "bad_mailbox", | ||
message_category: "test", | ||
message_client_ip: "192.168.1.1", | ||
message_created_at: "2023-01-01T00:00:00Z", | ||
message_outgoing_ip: "10.0.0.1", | ||
message_recipient_mx_name: "mx.example.com", | ||
message_sender_email: "sender@example.com", | ||
message_subject: "Test Email", | ||
}; | ||
|
||
const mockSuppressions: Suppression[] = [ | ||
{ | ||
id: "1", | ||
email: "test1@example.com", | ||
type: "hard bounce", | ||
created_at: "2023-01-01T00:00:00Z", | ||
sending_stream: "transactional", | ||
domain_name: "example.com", | ||
message_bounce_category: "bad_mailbox", | ||
message_category: "test", | ||
message_client_ip: "192.168.1.1", | ||
message_created_at: "2023-01-01T00:00:00Z", | ||
message_outgoing_ip: "10.0.0.1", | ||
message_recipient_mx_name: "mx.example.com", | ||
message_sender_email: "sender@example.com", | ||
message_subject: "Test Email 1", | ||
}, | ||
{ | ||
id: "2", | ||
email: "test2@example.com", | ||
type: "spam complaint", | ||
created_at: "2023-01-02T00:00:00Z", | ||
sending_stream: "bulk", | ||
domain_name: "example.com", | ||
message_bounce_category: null, | ||
message_category: "promotional", | ||
message_client_ip: "192.168.1.2", | ||
message_created_at: "2023-01-02T00:00:00Z", | ||
message_outgoing_ip: "10.0.0.2", | ||
message_recipient_mx_name: "mx.example.com", | ||
message_sender_email: "sender@example.com", | ||
message_subject: "Test Email 2", | ||
}, | ||
]; | ||
|
||
describe("class SuppressionsApi(): ", () => { | ||
describe("init: ", () => { | ||
it("initializes with all necessary params.", () => { | ||
expect(suppressionsAPI).toHaveProperty("getList"); | ||
expect(suppressionsAPI).toHaveProperty("delete"); | ||
}); | ||
}); | ||
}); | ||
|
||
beforeAll(() => { | ||
axios.interceptors.response.use( | ||
(response) => response.data, | ||
handleSendingError | ||
); | ||
mock = new AxiosMockAdapter(axios); | ||
}); | ||
|
||
afterEach(() => { | ||
mock.reset(); | ||
}); | ||
|
||
describe("getList(): ", () => { | ||
it("successfully gets all suppressions.", async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not all, it's just first up to 1000. See my other comment https://github.com/railsware/mailtrap-nodejs/pull/68/files#r2160850757 |
||
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/suppressions`; | ||
|
||
expect.assertions(2); | ||
|
||
mock.onGet(endpoint).reply(200, mockSuppressions); | ||
const result = await suppressionsAPI.getList(); | ||
|
||
expect(mock.history.get[0].url).toEqual(endpoint); | ||
expect(result).toEqual(mockSuppressions); | ||
}); | ||
|
||
it("successfully gets suppressions filtered by email.", async () => { | ||
const email = "test@example.com"; | ||
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/suppressions`; | ||
|
||
expect.assertions(3); | ||
|
||
mock.onGet(endpoint, { params: { email } }).reply(200, [mockSuppression]); | ||
const result = await suppressionsAPI.getList(email); | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
expect(mock.history.get[0].url).toEqual(endpoint); | ||
expect(mock.history.get[0].params).toEqual({ email }); | ||
expect(result).toEqual([mockSuppression]); | ||
}); | ||
|
||
it("fails with error.", async () => { | ||
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/suppressions`; | ||
const expectedErrorMessage = "Request failed with status code 400"; | ||
|
||
expect.assertions(2); | ||
|
||
mock.onGet(endpoint).reply(400, { error: expectedErrorMessage }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a blocker, but this test case is a bit unrealistic. There's no way we can get 400 from this endpoint. We documented 200, 401 and 403 only https://api-docs.mailtrap.io/docs/mailtrap-api-docs/f8144826d885a-list-and-search-suppressions |
||
|
||
try { | ||
await suppressionsAPI.getList(); | ||
} catch (error) { | ||
expect(error).toBeInstanceOf(MailtrapError); | ||
if (error instanceof MailtrapError) { | ||
expect(error.message).toEqual(expectedErrorMessage); | ||
} | ||
} | ||
}); | ||
}); | ||
|
||
describe("delete(): ", () => { | ||
const suppressionId = "1"; | ||
|
||
it("successfully deletes a suppression.", async () => { | ||
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/suppressions/${suppressionId}`; | ||
|
||
expect.assertions(1); | ||
|
||
mock.onDelete(endpoint).reply(204); | ||
await suppressionsAPI.delete(suppressionId); | ||
|
||
expect(mock.history.delete[0].url).toEqual(endpoint); | ||
}); | ||
|
||
it("fails with error.", async () => { | ||
const endpoint = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/suppressions/${suppressionId}`; | ||
const expectedErrorMessage = "Request failed with status code 404"; | ||
|
||
expect.assertions(2); | ||
|
||
mock.onDelete(endpoint).reply(404, { error: expectedErrorMessage }); | ||
|
||
try { | ||
await suppressionsAPI.delete(suppressionId); | ||
} catch (error) { | ||
expect(error).toBeInstanceOf(MailtrapError); | ||
if (error instanceof MailtrapError) { | ||
expect(error.message).toEqual(expectedErrorMessage); | ||
} | ||
} | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ import { | |
BatchSendResponse, | ||
BatchSendRequest, | ||
} from "../types/mailtrap"; | ||
import SuppressionsBaseAPI from "./api/Suppressions"; | ||
|
||
const { CLIENT_SETTINGS, ERRORS } = CONFIG; | ||
const { | ||
|
@@ -132,12 +133,26 @@ export default class MailtrapClient { | |
return new ContactListsBaseAPI(this.axios, this.accountId); | ||
} | ||
|
||
/** | ||
* Getter for Templates API. | ||
*/ | ||
get templates() { | ||
this.validateAccountIdPresence(); | ||
|
||
return new TemplatesBaseAPI(this.axios, this.accountId); | ||
} | ||
|
||
/** | ||
* Getter for Suppressions API. | ||
*/ | ||
get suppressions() { | ||
if (!this.accountId) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please rebase |
||
throw new MailtrapError(ACCOUNT_ID_MISSING); | ||
} | ||
|
||
return new SuppressionsBaseAPI(this.axios, this.accountId); | ||
} | ||
|
||
/** | ||
* Returns configured host. Checks if `bulk` and `sandbox` modes are activated simultaneously, | ||
* then reject with Mailtrap Error. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { AxiosInstance } from "axios"; | ||
|
||
import SuppressionsApi from "./resources/Suppressions"; | ||
|
||
export default class SuppressionsBaseAPI { | ||
private client: AxiosInstance; | ||
|
||
private accountId?: number; | ||
|
||
public getList: SuppressionsApi["getList"]; | ||
|
||
public delete: SuppressionsApi["delete"]; | ||
|
||
constructor(client: AxiosInstance, accountId?: number) { | ||
this.client = client; | ||
this.accountId = accountId; | ||
const suppressions = new SuppressionsApi(this.client, this.accountId); | ||
this.getList = suppressions.getList.bind(suppressions); | ||
this.delete = suppressions.delete.bind(suppressions); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { AxiosInstance } from "axios"; | ||
|
||
import CONFIG from "../../../config"; | ||
import { ListOptions, Suppression } from "../../../types/api/suppressions"; | ||
|
||
const { CLIENT_SETTINGS } = CONFIG; | ||
const { GENERAL_ENDPOINT } = CLIENT_SETTINGS; | ||
|
||
export default class SuppressionsApi { | ||
private client: AxiosInstance; | ||
|
||
private accountId?: number; | ||
|
||
private suppressionsURL: string; | ||
|
||
constructor(client: AxiosInstance, accountId?: number) { | ||
this.client = client; | ||
this.accountId = accountId; | ||
this.suppressionsURL = `${GENERAL_ENDPOINT}/api/accounts/${this.accountId}/suppressions`; | ||
} | ||
|
||
/** | ||
* List and search suppressions by email. The endpoint returns up to 1000 suppressions per request. | ||
*/ | ||
public async getList(options?: ListOptions) { | ||
const params = { | ||
...(options?.email && { email: options.email }), | ||
}; | ||
|
||
return this.client.get<Suppression[], Suppression[]>(this.suppressionsURL, { | ||
params, | ||
}); | ||
} | ||
|
||
/** | ||
* Delete a suppression by ID. | ||
* Mailtrap will no longer prevent sending to this email unless it's recorded in suppressions again. | ||
*/ | ||
public async delete(id: string) { | ||
return this.client.delete<Suppression, Suppression>( | ||
`${this.suppressionsURL}/${id}` | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
export type Suppression = { | ||
id: string; | ||
type: "hard bounce" | "spam complaint" | "unsubscription" | "manual import"; | ||
created_at: string; | ||
email: string; | ||
sending_stream: "transactional" | "bulk"; | ||
domain_name: string | null; | ||
message_bounce_category: string | null; | ||
message_category: string | null; | ||
message_client_ip: string | null; | ||
message_created_at: string | null; | ||
message_outgoing_ip: string | null; | ||
message_recipient_mx_name: string | null; | ||
message_sender_email: string | null; | ||
message_subject: string | null; | ||
}; | ||
|
||
export type ListOptions = { | ||
email?: string; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Saying "all" in this case is unfortunate, because it doesn't actually return all suppressions. As the docs say, the endpoint returns up to 1000 suppressions per request (hard limit, and there's no pagination - to find something specific, a query filter should be used).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@narekhovhannisyan this was not corrected yet