Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,13 @@ Email Sandbox (Testing):
- Project management CRUD – [`testing/projects.ts`](examples/testing/projects.ts)
- Attachments operations – [`testing/attachments.ts`](examples/testing/attachments.ts)

Inbound Email:

- Folders CRUD – [`inbound/folders.ts`](examples/inbound/folders.ts)
- Inboxes CRUD (hosted & custom-domain) – [`inbound/inboxes.ts`](examples/inbound/inboxes.ts)
- Messages (list, get, delete, reply, reply-all, forward) – [`inbound/messages.ts`](examples/inbound/messages.ts)
- Threads (list, get, delete) – [`inbound/threads.ts`](examples/inbound/threads.ts)

Contact management:

- Contacts CRUD & listing – [`contacts/everything.ts`](examples/contacts/everything.ts)
Expand Down
29 changes: 29 additions & 0 deletions examples/inbound/folders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { MailtrapClient } from "mailtrap";

const TOKEN = "<YOUR-TOKEN-HERE>";

// Inbound is scoped to the token's account, so no accountId is required.
const client = new MailtrapClient({ token: TOKEN });
const foldersClient = client.inbound.folders;

async function foldersFlow() {
try {
const created = await foldersClient.create({ name: "Support" });
console.log("Created folder:", created);

console.log("All folders:", await foldersClient.getList());
console.log("One folder:", await foldersClient.get(created.id));

const updated = await foldersClient.update(created.id, {
name: "Customer Success",
});
console.log("Updated folder:", updated);

await foldersClient.delete(created.id);
console.log("Deleted folder", created.id);
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
}
}

foldersFlow();
32 changes: 32 additions & 0 deletions examples/inbound/inboxes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { MailtrapClient } from "mailtrap";

const TOKEN = "<YOUR-TOKEN-HERE>";
const FOLDER_ID = Number("<YOUR-FOLDER-ID-HERE>");

// Inbound is scoped to the token's account, so no accountId is required.
const client = new MailtrapClient({ token: TOKEN });
const inboxesClient = client.inbound.inboxes;

async function inboxesFlow() {
try {
// Omit domain_id for a Mailtrap-hosted inbox; pass it to create a
// custom-domain (catch-all) inbox.
const created = await inboxesClient.create(FOLDER_ID, { name: "Tickets" });
console.log("Created inbox:", created);

console.log("All inboxes:", await inboxesClient.getList(FOLDER_ID));
console.log("One inbox:", await inboxesClient.get(FOLDER_ID, created.id));

const updated = await inboxesClient.update(FOLDER_ID, created.id, {
name: "Tickets (renamed)",
});
console.log("Updated inbox:", updated);

await inboxesClient.delete(FOLDER_ID, created.id);
console.log("Deleted inbox", created.id);
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
}
}

inboxesFlow();
66 changes: 66 additions & 0 deletions examples/inbound/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { MailtrapClient } from "mailtrap";

const TOKEN = "<YOUR-TOKEN-HERE>";
const INBOX_ID = Number("<YOUR-INBOX-ID-HERE>");

// Set to a real message id to try the mutating actions (reply / reply-all /
// forward / delete). Left empty, the example is read-only (list + get).
const MESSAGE_ID = "";

// Inbound is scoped to the token's account, so no accountId is required.
const client = new MailtrapClient({ token: TOKEN });
const messagesClient = client.inbound.messages;

async function messagesFlow() {
try {
const list = await messagesClient.getList(INBOX_ID);
console.log("Messages:", list);

// Fetch the next page with the returned cursor.
if (list.last_id) {
console.log(
"Next page:",
await messagesClient.getList(INBOX_ID, { last_id: list.last_id })
);
}

if (list.data.length > 0) {
console.log(
"First message:",
await messagesClient.get(INBOX_ID, list.data[0].id)
);
}

if (!MESSAGE_ID) {
console.log(
"Set MESSAGE_ID above to try reply / reply-all / forward / delete."
);
return;
}

// These act on a real message: reply/reply_all/forward SEND email, and
// delete removes the message. Only run against a message you own.
console.log(
"Reply:",
await messagesClient.reply(INBOX_ID, MESSAGE_ID, {
text: "Thanks for reaching out — we are looking into it.",
})
);

await messagesClient.replyAll(INBOX_ID, MESSAGE_ID, {
text: "Looping everyone in.",
});

await messagesClient.forward(INBOX_ID, MESSAGE_ID, {
to: [{ email: "colleague@example.com" }],
text: "Please take a look.",
});

await messagesClient.delete(INBOX_ID, MESSAGE_ID);
console.log("Deleted message", MESSAGE_ID);
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
}
}

messagesFlow();
47 changes: 47 additions & 0 deletions examples/inbound/threads.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 INBOX_ID = Number("<YOUR-INBOX-ID-HERE>");

// Set to a real thread id to try deletion below. Left empty, the example is
// read-only (list + get).
const THREAD_ID = "";

// Inbound is scoped to the token's account, so no accountId is required.
const client = new MailtrapClient({ token: TOKEN });
const threadsClient = client.inbound.threads;

async function threadsFlow() {
try {
const list = await threadsClient.getList(INBOX_ID);
console.log("Threads:", list);

// Fetch the next page with the returned cursor.
if (list.last_id) {
console.log(
"Next page:",
await threadsClient.getList(INBOX_ID, { last_id: list.last_id })
);
}

if (list.data.length > 0) {
console.log(
"First thread:",
await threadsClient.get(INBOX_ID, list.data[0].id)
);
}

if (!THREAD_ID) {
console.log("Set THREAD_ID above to try deletion.");
return;
}

// Deletes a real thread — only run against one you own.
await threadsClient.delete(INBOX_ID, THREAD_ID);
console.log("Deleted thread", THREAD_ID);
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
}
}

threadsFlow();
2 changes: 1 addition & 1 deletion examples/sending-domains/everything.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MailtrapClient } from "../../src/index";
import { MailtrapClient } from "mailtrap";

const TOKEN = "<YOUR-TOKEN-HERE>";
const ACCOUNT_ID = "<YOUR-ACCOUNT-ID-HERE>";
Expand Down
40 changes: 40 additions & 0 deletions src/__tests__/lib/api/Inbound.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import axios from "axios";

import InboundAPI from "../../../lib/api/Inbound";

describe("lib/api/Inbound: ", () => {
const inbound = new InboundAPI(axios.create());

describe("class InboundAPI(): ", () => {
it("exposes folders with CRUD methods.", () => {
expect(inbound.folders).toHaveProperty("getList");
expect(inbound.folders).toHaveProperty("get");
expect(inbound.folders).toHaveProperty("create");
expect(inbound.folders).toHaveProperty("update");
expect(inbound.folders).toHaveProperty("delete");
});

it("exposes inboxes with CRUD methods.", () => {
expect(inbound.inboxes).toHaveProperty("getList");
expect(inbound.inboxes).toHaveProperty("get");
expect(inbound.inboxes).toHaveProperty("create");
expect(inbound.inboxes).toHaveProperty("update");
expect(inbound.inboxes).toHaveProperty("delete");
});

it("exposes messages with list/get/delete and send methods.", () => {
expect(inbound.messages).toHaveProperty("getList");
expect(inbound.messages).toHaveProperty("get");
expect(inbound.messages).toHaveProperty("delete");
expect(inbound.messages).toHaveProperty("reply");
expect(inbound.messages).toHaveProperty("replyAll");
expect(inbound.messages).toHaveProperty("forward");
});

it("exposes threads with list/get/delete methods.", () => {
expect(inbound.threads).toHaveProperty("getList");
expect(inbound.threads).toHaveProperty("get");
expect(inbound.threads).toHaveProperty("delete");
});
});
});
4 changes: 4 additions & 0 deletions src/__tests__/lib/api/resources/EmailLogs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ describe("lib/api/resources/EmailLogs: ", () => {
message_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
status: "delivered",
subject: "Welcome",
rfc_message_id: "<abc@example.com>",
in_reply_to: null,
references: [],
thread_id: null,
from: "sender@example.com",
to: "recipient@example.com",
sent_at: "2025-01-15T10:30:00Z",
Expand Down
6 changes: 6 additions & 0 deletions src/__tests__/lib/api/resources/SendingDomains.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ describe("lib/api/SendingDomains: ", () => {
health_alerts_enabled: true,
critical_alerts_enabled: true,
alert_recipient_email: "john.doe@example.com",
inbound_enabled: true,
inbound_verified: true,
permissions: mockPermissions,
},
];
Expand Down Expand Up @@ -125,6 +127,8 @@ describe("lib/api/SendingDomains: ", () => {
health_alerts_enabled: true,
critical_alerts_enabled: true,
alert_recipient_email: "john.doe@example.com",
inbound_enabled: true,
inbound_verified: true,
permissions: mockPermissions,
};

Expand Down Expand Up @@ -174,6 +178,8 @@ describe("lib/api/SendingDomains: ", () => {
health_alerts_enabled: true,
critical_alerts_enabled: true,
alert_recipient_email: "admin@newdomain.com",
inbound_enabled: true,
inbound_verified: true,
permissions: mockPermissions,
};

Expand Down
79 changes: 79 additions & 0 deletions src/__tests__/lib/api/resources/inbound/Folders.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import axios, { AxiosInstance } from "axios";
import MockAdapter from "axios-mock-adapter";

import FoldersApi from "../../../../../lib/api/resources/inbound/Folders";
import { Folder } from "../../../../../types/api/inbound/folders";

describe("lib/api/resources/inbound/Folders: ", () => {
const axiosInstance: AxiosInstance = axios.create();
const mock = new MockAdapter(axiosInstance);
axiosInstance.interceptors.response.use((response) => response.data);

const foldersAPI = new FoldersApi(axiosInstance);
const foldersURL = "https://mailtrap.io/api/inbound/folders";

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

describe("getList(): ", () => {
it("returns all folders.", async () => {
const folders: Folder[] = [
{ id: 1, name: "Support" },
{ id: 2, name: "Sales" },
];

mock.onGet(foldersURL).reply(200, folders);

const result = await foldersAPI.getList();

expect(result).toEqual(folders);
});
});

describe("get(): ", () => {
it("returns a single folder by id.", async () => {
const folder: Folder = { id: 1, name: "Support" };

mock.onGet(`${foldersURL}/1`).reply(200, folder);

const result = await foldersAPI.get(1);

expect(result).toEqual(folder);
});
});

describe("create(): ", () => {
it("creates a folder.", async () => {
const params = { name: "Support" };
const folder: Folder = { id: 1, name: "Support" };

mock.onPost(foldersURL, params).reply(201, folder);

const result = await foldersAPI.create(params);

expect(result).toEqual(folder);
});
});

describe("update(): ", () => {
it("updates a folder.", async () => {
const params = { name: "Customer Success" };
const folder: Folder = { id: 1, name: "Customer Success" };

mock.onPatch(`${foldersURL}/1`, params).reply(200, folder);

const result = await foldersAPI.update(1, params);

expect(result).toEqual(folder);
});
});

describe("delete(): ", () => {
it("deletes a folder.", async () => {
mock.onDelete(`${foldersURL}/1`).reply(204);

const result = await foldersAPI.delete(1);

expect(result).toBeUndefined();
});
});
});
Loading
Loading