diff --git a/examples/sending-domains/everything.ts b/examples/sending-domains/everything.ts new file mode 100644 index 0000000..a2f361b --- /dev/null +++ b/examples/sending-domains/everything.ts @@ -0,0 +1,48 @@ +import { MailtrapClient } from "../../src/index"; + +const TOKEN = ""; +const ACCOUNT_ID = ""; + +const client = new MailtrapClient({ + token: TOKEN, + accountId: Number(ACCOUNT_ID), +}); + +async function sendingDomainsFlow() { + try { + // Get all sending domains + const all = await client.sendingDomains.getList(); + console.log("All sending domains:", JSON.stringify(all, null, 2)); + + if (!all.data || all.data.length === 0) { + console.log("No sending domains found for this account."); + return; + } + + // Get a specific sending domain + const one = await client.sendingDomains.get(all.data[0].id); + console.log("One sending domain:", JSON.stringify(one, null, 2)); + + // Send setup instructions + const setupResponse = await client.sendingDomains.sendSetupInstructions( + all.data[0].id, + "admin@example.com" + ); + console.log("Setup instructions sent"); + + // Create a new sending domain + const created = await client.sendingDomains.create({ + domain_name: "test-domain-" + Date.now() + ".com", + }); + console.log("Created sending domain:", JSON.stringify(created, null, 2)); + + // Delete the created domain + await client.sendingDomains.delete(created.id); + console.log("Sending domain deleted"); + + } catch (error) { + console.error("Error in sendingDomainsFlow:", error instanceof Error ? error.message : String(error)); + } +} + +sendingDomainsFlow(); diff --git a/src/__tests__/lib/api/resources/SendingDomains.test.ts b/src/__tests__/lib/api/resources/SendingDomains.test.ts new file mode 100644 index 0000000..7f6193f --- /dev/null +++ b/src/__tests__/lib/api/resources/SendingDomains.test.ts @@ -0,0 +1,233 @@ +import axios, { AxiosInstance } from "axios"; +import MockAdapter from "axios-mock-adapter"; + +import SendingDomainsBaseAPI from "../../../../lib/api/SendingDomains"; +import { + SendingDomain, + DnsRecord, + SendingDomainPermissions, +} from "../../../../types/api/sending-domains"; + +describe("lib/api/SendingDomains: ", () => { + const axiosInstance: AxiosInstance = axios.create(); + const mock = new MockAdapter(axiosInstance); + + // Add the response interceptor that returns response.data + axiosInstance.interceptors.response.use((response) => response.data); + + const testAccountId = 100; + const sendingDomainsAPI = new SendingDomainsBaseAPI( + axiosInstance, + testAccountId + ); + + describe("class SendingDomainsBaseAPI(): ", () => { + describe("init: ", () => { + it("initializes with all necessary params.", () => { + expect(sendingDomainsAPI).toHaveProperty("get"); + expect(sendingDomainsAPI).toHaveProperty("getList"); + expect(sendingDomainsAPI).toHaveProperty("create"); + expect(sendingDomainsAPI).toHaveProperty("delete"); + expect(sendingDomainsAPI).toHaveProperty("sendSetupInstructions"); + }); + }); + + describe("sendingDomains.getList(): ", () => { + it("should get sending domains list.", async () => { + const mockDnsRecords: DnsRecord[] = [ + { + key: "verification", + domain: "ve6wza2rbpe60x7z.example.com", + type: "CNAME", + value: "smtp.mailtrap.live", + status: "pass", + name: "ve6wza2rbpe60x7z", + }, + { + key: "spf", + domain: "example.com", + type: "TXT", + value: "v=spf1 include:_spf.smtp.mailtrap.live ~all", + status: "pass", + name: "", + }, + ]; + + const mockPermissions: SendingDomainPermissions = { + can_read: true, + can_update: true, + can_destroy: true, + }; + + const mockSendingDomains: SendingDomain[] = [ + { + id: 435, + domain_name: "example.com", + demo: false, + compliance_status: "compliant", + dns_verified: true, + dns_verified_at: "2024-12-26T09:40:44.161Z", + dns_records: mockDnsRecords, + open_tracking_enabled: true, + click_tracking_enabled: true, + auto_unsubscribe_link_enabled: true, + custom_domain_tracking_enabled: true, + health_alerts_enabled: true, + critical_alerts_enabled: true, + alert_recipient_email: "john.doe@example.com", + permissions: mockPermissions, + }, + ]; + + mock + .onGet( + `https://mailtrap.io/api/accounts/${testAccountId}/sending_domains` + ) + .reply(200, { data: mockSendingDomains }); + + const result = await sendingDomainsAPI.getList(); + + expect(result).toEqual({ data: mockSendingDomains }); + }); + }); + + describe("sendingDomains.get(): ", () => { + it("should get a single sending domain by id.", async () => { + const mockDnsRecords: DnsRecord[] = [ + { + key: "verification", + domain: "ve6wza2rbpe60x7z.example.com", + type: "CNAME", + value: "smtp.mailtrap.live", + status: "pass", + name: "ve6wza2rbpe60x7z", + }, + ]; + + const mockPermissions: SendingDomainPermissions = { + can_read: true, + can_update: true, + can_destroy: true, + }; + + const mockSendingDomain: SendingDomain = { + id: 999, + domain_name: "example.com", + demo: false, + compliance_status: "compliant", + dns_verified: true, + dns_verified_at: "2024-12-26T09:40:44.161Z", + dns_records: mockDnsRecords, + open_tracking_enabled: true, + click_tracking_enabled: true, + auto_unsubscribe_link_enabled: true, + custom_domain_tracking_enabled: true, + health_alerts_enabled: true, + critical_alerts_enabled: true, + alert_recipient_email: "john.doe@example.com", + permissions: mockPermissions, + }; + + mock + .onGet( + `https://mailtrap.io/api/accounts/${testAccountId}/sending_domains/${mockSendingDomain.id}` + ) + .reply(200, mockSendingDomain); + + const result = await sendingDomainsAPI.get(mockSendingDomain.id); + + expect(result).toEqual(mockSendingDomain); + }); + }); + + describe("sendingDomains.create(): ", () => { + it("should create a new sending domain.", async () => { + const mockDnsRecords: DnsRecord[] = [ + { + key: "verification", + domain: "ve6wza2rbpe60x7z.newdomain.com", + type: "CNAME", + value: "smtp.mailtrap.live", + status: "pass", + name: "ve6wza2rbpe60x7z", + }, + ]; + + const mockPermissions: SendingDomainPermissions = { + can_read: true, + can_update: true, + can_destroy: true, + }; + + const mockSendingDomain: SendingDomain = { + id: 436, + domain_name: "newdomain.com", + demo: false, + compliance_status: "pending", + dns_verified: false, + dns_verified_at: "", + dns_records: mockDnsRecords, + open_tracking_enabled: true, + click_tracking_enabled: true, + auto_unsubscribe_link_enabled: true, + custom_domain_tracking_enabled: true, + health_alerts_enabled: true, + critical_alerts_enabled: true, + alert_recipient_email: "admin@newdomain.com", + permissions: mockPermissions, + }; + + const createParams = { + domain_name: "newdomain.com", + }; + + mock + .onPost( + `https://mailtrap.io/api/accounts/${testAccountId}/sending_domains`, + { sending_domain: createParams } + ) + .reply(201, mockSendingDomain); + + const result = await sendingDomainsAPI.create(createParams); + + expect(result).toEqual(mockSendingDomain); + }); + }); + + describe("sendingDomains.delete(): ", () => { + it("should delete a sending domain by id.", async () => { + const sendingDomainId = 999; + + mock + .onDelete( + `https://mailtrap.io/api/accounts/${testAccountId}/sending_domains/${sendingDomainId}` + ) + .reply(204); + + const result = await sendingDomainsAPI.delete(sendingDomainId); + + expect(result).toBeUndefined(); + }); + }); + + describe("sendingDomains.sendSetupInstructions(): ", () => { + it("should send setup instructions for a sending domain.", async () => { + const sendingDomainId = 999; + const email = "admin@example.com"; + + mock + .onPost( + `https://mailtrap.io/api/accounts/${testAccountId}/sending_domains/${sendingDomainId}/send_setup_instructions` + ) + .reply(204); + + const result = await sendingDomainsAPI.sendSetupInstructions( + sendingDomainId, + email + ); + + expect(result).toBeUndefined(); + }); + }); + }); +}); diff --git a/src/__tests__/lib/mailtrap-client.test.ts b/src/__tests__/lib/mailtrap-client.test.ts index 65b5890..c4123a4 100644 --- a/src/__tests__/lib/mailtrap-client.test.ts +++ b/src/__tests__/lib/mailtrap-client.test.ts @@ -13,6 +13,7 @@ import ContactLists from "../../lib/api/ContactLists"; import Contacts from "../../lib/api/Contacts"; import TemplatesBaseAPI from "../../lib/api/Templates"; import SuppressionsBaseAPI from "../../lib/api/Suppressions"; +import SendingDomainsBaseAPI from "../../lib/api/SendingDomains"; const { ERRORS, CLIENT_SETTINGS } = CONFIG; const { TESTING_ENDPOINT, BULK_ENDPOINT, SENDING_ENDPOINT } = CLIENT_SETTINGS; @@ -873,5 +874,31 @@ describe("lib/mailtrap-client: ", () => { expect(suppressionsClient).toBeInstanceOf(SuppressionsBaseAPI); }); }); + + describe("get sendingDomains(): ", () => { + it("rejects with Mailtrap error, when `accountId` is missing.", () => { + expect.assertions(1); + + const client = new MailtrapClient({ + token: "test-token", + }); + + expect(() => client.sendingDomains).toThrow( + "accountId is missing, some features of testing API may not work properly." + ); + }); + + it("returns sending domains API object when accountId is provided.", () => { + expect.assertions(1); + + const client = new MailtrapClient({ + token: "test-token", + accountId: 123, + }); + + const sendingDomainsClient = client.sendingDomains; + expect(sendingDomainsClient).toBeInstanceOf(SendingDomainsBaseAPI); + }); + }); }); }); diff --git a/src/lib/MailtrapClient.ts b/src/lib/MailtrapClient.ts index f88f65a..baa6bb2 100644 --- a/src/lib/MailtrapClient.ts +++ b/src/lib/MailtrapClient.ts @@ -14,6 +14,7 @@ import ContactListsBaseAPI from "./api/ContactLists"; import ContactFieldsBaseAPI from "./api/ContactFields"; import TemplatesBaseAPI from "./api/Templates"; import SuppressionsBaseAPI from "./api/Suppressions"; +import SendingDomainsBaseAPI from "./api/SendingDomains"; import CONFIG from "../config"; @@ -161,6 +162,15 @@ export default class MailtrapClient { return new SuppressionsBaseAPI(this.axios, accountId); } + /** + * Getter for Sending Domains API. + */ + get sendingDomains() { + this.validateAccountIdPresence(); + + return new SendingDomainsBaseAPI(this.axios, this.accountId!); + } + /** * Returns configured host. Checks if `bulk` and `sandbox` modes are activated simultaneously, * then reject with Mailtrap Error. diff --git a/src/lib/api/SendingDomains.ts b/src/lib/api/SendingDomains.ts new file mode 100644 index 0000000..db1f623 --- /dev/null +++ b/src/lib/api/SendingDomains.ts @@ -0,0 +1,28 @@ +import { AxiosInstance } from "axios"; + +import SendingDomainsApi from "./resources/SendingDomains"; + +export default class SendingDomainsBaseAPI { + private client: AxiosInstance; + + public get: SendingDomainsApi["get"]; + + public getList: SendingDomainsApi["getList"]; + + public create: SendingDomainsApi["create"]; + + public delete: SendingDomainsApi["delete"]; + + public sendSetupInstructions: SendingDomainsApi["sendSetupInstructions"]; + + constructor(client: AxiosInstance, accountId: number) { + this.client = client; + const sendingDomains = new SendingDomainsApi(this.client, accountId); + this.get = sendingDomains.get.bind(sendingDomains); + this.getList = sendingDomains.getList.bind(sendingDomains); + this.create = sendingDomains.create.bind(sendingDomains); + this.delete = sendingDomains.delete.bind(sendingDomains); + this.sendSetupInstructions = + sendingDomains.sendSetupInstructions.bind(sendingDomains); + } +} diff --git a/src/lib/api/resources/SendingDomains.ts b/src/lib/api/resources/SendingDomains.ts new file mode 100644 index 0000000..d95b041 --- /dev/null +++ b/src/lib/api/resources/SendingDomains.ts @@ -0,0 +1,75 @@ +import { AxiosInstance } from "axios"; + +import CONFIG from "../../../config"; +import { + CreateSendingDomainParams, + SendingDomain, + SendingDomainsResponse, +} from "../../../types/api/sending-domains"; + +const { CLIENT_SETTINGS } = CONFIG; +const { GENERAL_ENDPOINT } = CLIENT_SETTINGS; + +export default class SendingDomainsApi { + private client: AxiosInstance; + + private sendingDomainsURL: string; + + constructor(client: AxiosInstance, accountId: number) { + this.client = client; + this.sendingDomainsURL = `${GENERAL_ENDPOINT}/api/accounts/${accountId}/sending_domains`; + } + + /** + * Get a list of sending domains. + * @returns Returns the list of sending domains for the account. + */ + public async getList() { + const url = this.sendingDomainsURL; + + return this.client.get(url); + } + + /** + * Get a single sending domain by ID. + * @param id Sending domain ID + * @returns Returns a single sending domain + */ + public async get(id: number) { + const url = `${this.sendingDomainsURL}/${id}`; + + return this.client.get(url); + } + + /** + * Create a new sending domain. + */ + public async create(params: CreateSendingDomainParams) { + const url = this.sendingDomainsURL; + const data = { sending_domain: params }; + + return this.client.post(url, data); + } + + /** + * Delete a sending domain by ID. + * @param id Sending domain ID + */ + public async delete(id: number) { + const url = `${this.sendingDomainsURL}/${id}`; + + return this.client.delete(url); + } + + /** + * Send setup instructions for a sending domain to an email address. + * @param id Sending domain ID + * @param email Email address to send setup instructions to + * @returns Returns a success message + */ + public async sendSetupInstructions(id: number, email: string) { + const url = `${this.sendingDomainsURL}/${id}/send_setup_instructions`; + + return this.client.post(url, { email }); + } +} diff --git a/src/types/api/contact-fields.ts b/src/types/api/contact-fields.ts index 410368b..2beba43 100644 --- a/src/types/api/contact-fields.ts +++ b/src/types/api/contact-fields.ts @@ -1,3 +1,8 @@ +/** + * Contact field definition/schema used for managing field types. + * Represents the structure and metadata of a contact field (e.g., field name, data type, merge tag). + * Used by Contact Fields API for CRUD operations on field definitions. + */ export interface ContactField { id: number; name: string; diff --git a/src/types/api/sending-domains.ts b/src/types/api/sending-domains.ts new file mode 100644 index 0000000..4333867 --- /dev/null +++ b/src/types/api/sending-domains.ts @@ -0,0 +1,40 @@ +export interface DnsRecord { + key: string; + domain: string; + type: string; + value: string; + status: string; + name: string; +} + +export interface SendingDomainPermissions { + can_read: boolean; + can_update: boolean; + can_destroy: boolean; +} + +export interface SendingDomain { + id: number; + domain_name: string; + demo: boolean; + compliance_status: string; + dns_verified: boolean; + dns_verified_at: string | null; + dns_records: DnsRecord[]; + open_tracking_enabled: boolean; + click_tracking_enabled: boolean; + auto_unsubscribe_link_enabled: boolean; + custom_domain_tracking_enabled: boolean; + health_alerts_enabled: boolean; + critical_alerts_enabled: boolean; + alert_recipient_email: string | null; + permissions: SendingDomainPermissions; +} + +export interface CreateSendingDomainParams { + domain_name: string; +} + +export interface SendingDomainsResponse { + data: SendingDomain[]; +} diff --git a/src/types/mailtrap.ts b/src/types/mailtrap.ts index baa5982..2ad9714 100644 --- a/src/types/mailtrap.ts +++ b/src/types/mailtrap.ts @@ -125,6 +125,10 @@ export interface BatchSendRequest { }[]; } +/** + * Dynamic contact field values used when creating or updating contacts. + * Represents the actual data stored in contact fields (e.g., first_name: "John", phone: "123-456-7890"). + */ export interface ContactFields { [key: string]: string | number | boolean | undefined; }