diff --git a/README.md b/README.md
index 3fc9833..f497b4c 100644
--- a/README.md
+++ b/README.md
@@ -66,6 +66,13 @@ Refer to the [`examples`](examples) folder for the source code of this and other
- [Send email using template](examples/sending/template.ts)
- [Nodemailer transport](examples/sending/transport.ts)
+### Batch Sending API
+
+ - [Send a batch of transactional emails](examples/batch/transactional.ts)
+ - [Send a batch of bulk emails](examples/batch/bulk.ts)
+ - [Send a batch of sandbox emails](examples/batch/sandbox.ts)
+ - [Send a batch of emails from template](examples/batch/template.ts)
+
### Email testing API
- [Attachments](examples/testing/attachments.ts)
diff --git a/examples/batch/bulk.ts b/examples/batch/bulk.ts
new file mode 100644
index 0000000..a152e8a
--- /dev/null
+++ b/examples/batch/bulk.ts
@@ -0,0 +1,39 @@
+import { MailtrapClient } from "mailtrap";
+
+/**
+ * For this example, you need to have ready-to-use sending domain or,
+ * a Demo domain that allows sending emails to your own account email.
+ * @see https://help.mailtrap.io/article/69-sending-domain-setup
+ */
+
+const TOKEN = "";
+const SENDER_EMAIL = "";
+const RECIPIENT_EMAIL = "";
+
+const client = new MailtrapClient({
+ token: TOKEN,
+});
+
+client.batchSend({
+ base: {
+ from: { name: "Mailtrap Test", email: SENDER_EMAIL },
+ subject: "Sandbox Email",
+ text: "Welcome to Mailtrap Sandbox Batch Sending!"
+ },
+ requests: [
+ {
+ to: [{ email: RECIPIENT_EMAIL }],
+ custom_variables: {
+ email_number: 1
+ }
+ },
+ {
+ to: [{ email: RECIPIENT_EMAIL }],
+ custom_variables: {
+ email_number: 2
+ }
+ }
+ ]
+})
+ .then(console.log)
+ .catch(console.error);
\ No newline at end of file
diff --git a/examples/batch/sandbox.ts b/examples/batch/sandbox.ts
new file mode 100644
index 0000000..ac06ff7
--- /dev/null
+++ b/examples/batch/sandbox.ts
@@ -0,0 +1,42 @@
+import { MailtrapClient } from "mailtrap";
+
+/**
+ * For this example, you need to have ready-to-use sending domain or,
+ * a Demo domain that allows sending emails to your own account email.
+ * @see https://help.mailtrap.io/article/69-sending-domain-setup
+ */
+
+const TOKEN = "";
+const TEST_INBOX_ID = "";
+const SENDER_EMAIL = "";
+const RECIPIENT_EMAIL = "";
+
+const client = new MailtrapClient({
+ token: TOKEN,
+ sandbox: true,
+ testInboxId: TEST_INBOX_ID
+});
+
+client.batchSend({
+ base: {
+ from: { name: "Mailtrap Test", email: SENDER_EMAIL },
+ subject: "Sandbox Email",
+ text: "Welcome to Mailtrap Sandbox Batch Sending!"
+ },
+ requests: [
+ {
+ to: [{ email: RECIPIENT_EMAIL }],
+ custom_variables: {
+ email_number: 1
+ }
+ },
+ {
+ to: [{ email: RECIPIENT_EMAIL }],
+ custom_variables: {
+ email_number: 2
+ }
+ }
+ ]
+})
+ .then(console.log)
+ .catch(console.error);
diff --git a/examples/batch/template.ts b/examples/batch/template.ts
new file mode 100644
index 0000000..39649da
--- /dev/null
+++ b/examples/batch/template.ts
@@ -0,0 +1,39 @@
+import { MailtrapClient } from "mailtrap";
+
+/**
+ * For this example, you need to have ready-to-use sending domain or,
+ * a Demo domain that allows sending emails to your own account email.
+ * @see https://help.mailtrap.io/article/69-sending-domain-setup
+ */
+
+const TOKEN = "";
+const SENDER_EMAIL = "";
+const RECIPIENT_EMAIL = "";
+const TEMPLATE_UUID = "";
+
+const client = new MailtrapClient({ token: TOKEN });
+
+client.batchSend({
+ base: {
+ from: { name: "Mailtrap Test", email: SENDER_EMAIL },
+ template_uuid: TEMPLATE_UUID
+ },
+ requests: [
+ {
+ to: [{ email: RECIPIENT_EMAIL }],
+ template_variables: {
+ user_name: "John Doe",
+ company_name: "Example Corp"
+ }
+ },
+ {
+ to: [{ email: RECIPIENT_EMAIL }],
+ template_variables: {
+ user_name: "Jane Smith",
+ company_name: "Example Corp"
+ }
+ }
+ ]
+})
+ .then(console.log)
+ .catch(console.error);
diff --git a/examples/batch/transactional.ts b/examples/batch/transactional.ts
new file mode 100644
index 0000000..7e64388
--- /dev/null
+++ b/examples/batch/transactional.ts
@@ -0,0 +1,37 @@
+import { MailtrapClient } from "mailtrap";
+
+/**
+ * For this example, you need to have ready-to-use sending domain or,
+ * a Demo domain that allows sending emails to your own account email.
+ * @see https://help.mailtrap.io/article/69-sending-domain-setup
+ */
+
+const TOKEN = "";
+const SENDER_EMAIL = "";
+const RECIPIENT_EMAIL = "";
+
+const client = new MailtrapClient({ token: TOKEN });
+
+client.batchSend({
+ base: {
+ from: { name: "Mailtrap Test", email: SENDER_EMAIL },
+ subject: "Transactional Email",
+ text: "Welcome to Mailtrap Batch Sending!"
+ },
+ requests: [
+ {
+ to: [{ email: RECIPIENT_EMAIL }],
+ custom_variables: {
+ email_number: 1
+ }
+ },
+ {
+ to: [{ email: RECIPIENT_EMAIL }],
+ custom_variables: {
+ email_number: 2
+ }
+ }
+ ]
+})
+ .then(console.log)
+ .catch(console.error);
diff --git a/examples/testing/send-mail.ts b/examples/testing/send-mail.ts
index ca40e73..15bbde1 100644
--- a/examples/testing/send-mail.ts
+++ b/examples/testing/send-mail.ts
@@ -4,9 +4,9 @@ import { MailtrapClient } from "mailtrap"
* For this example, you need to have ready-to-use sending domain or,
* a Demo domain that allows sending emails to your own account email.
* @see https://help.mailtrap.io/article/69-sending-domain-setup
+ * @see https://help.mailtrap.io/article/69-sending-domain-setup#Demo-Domain--oYOU5"
*/
-", @see https://help.mailtrap.io/article/69-sending-domain-setup#Demo-Domain--oYOU5"
const TOKEN = "";
const TEST_INBOX_ID = ""
diff --git a/src/__tests__/lib/mailtrap-client.test.ts b/src/__tests__/lib/mailtrap-client.test.ts
index c510081..7de7fa7 100644
--- a/src/__tests__/lib/mailtrap-client.test.ts
+++ b/src/__tests__/lib/mailtrap-client.test.ts
@@ -327,6 +327,337 @@ describe("lib/mailtrap-client: ", () => {
}
}
});
+
+ describe("batch sending:", () => {
+ it("rejects with Mailtrap error when bulk and sandbox modes are used together", async () => {
+ const batchClient = new MailtrapClient({
+ token: "MY_API_TOKEN",
+ bulk: true,
+ sandbox: true,
+ });
+
+ const batchData = {
+ base: {
+ from: {
+ email: "sender@mailtrap.io",
+ name: "Mailtrap",
+ },
+ subject: "Batch Subject",
+ text: "Batch Text",
+ },
+ requests: [
+ {
+ to: [
+ {
+ email: "recipient1.mock@email.com",
+ name: "recipient1",
+ },
+ ],
+ },
+ ],
+ };
+
+ try {
+ await batchClient.batchSend(batchData);
+ } catch (error) {
+ expect(error).toBeInstanceOf(MailtrapError);
+ if (error instanceof MailtrapError) {
+ expect(error.message).toEqual(BULK_SANDBOX_INCOMPATIBLE);
+ }
+ }
+ });
+
+ it("successfully sends a batch of emails in bulk mode", async () => {
+ const batchClient = new MailtrapClient({
+ token: "MY_API_TOKEN",
+ bulk: true,
+ });
+ const endpoint = `${BULK_ENDPOINT}/api/batch`;
+ const expectedBatchResponseData = {
+ success: true,
+ responses: [
+ {
+ success: true,
+ message_ids: ["57f17fe0-3935-11f0-0000-f15507b34198"],
+ },
+ {
+ success: true,
+ message_ids: ["57f17fe0-3935-11f0-0000-f15507b34199"],
+ },
+ ],
+ };
+ mock.onPost(endpoint).reply(200, expectedBatchResponseData);
+
+ const batchData = {
+ base: {
+ from: {
+ email: "sender@mailtrap.io",
+ name: "Mailtrap",
+ },
+ subject: "Batch Subject",
+ text: "Batch Text",
+ },
+ requests: [
+ {
+ to: [
+ {
+ email: "recipient1.mock@email.com",
+ name: "recipient1",
+ },
+ ],
+ },
+ {
+ to: [
+ {
+ email: "recipient2.mock@email.com",
+ name: "recipient2",
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = await batchClient.batchSend(batchData);
+
+ expect(mock.history.post[0].url).toEqual(endpoint);
+ expect(mock.history.post[0].data).toEqual(
+ JSON.stringify({
+ base: batchData.base,
+ requests: batchData.requests.map((req) => ({
+ to: req.to,
+ })),
+ })
+ );
+ expect(result).toEqual(expectedBatchResponseData);
+ });
+
+ it("successfully sends a batch of emails in sandbox mode", async () => {
+ const batchClient = new MailtrapClient({
+ token: "MY_API_TOKEN",
+ sandbox: true,
+ testInboxId: 100,
+ });
+ const endpoint = `${TESTING_ENDPOINT}/api/batch/100`;
+ const expectedBatchResponseData = {
+ success: true,
+ responses: [
+ {
+ success: true,
+ message_ids: ["57f17fe0-3935-11f0-0000-f15507b34198"],
+ },
+ ],
+ };
+ mock.onPost(endpoint).reply(200, expectedBatchResponseData);
+
+ const batchData = {
+ base: {
+ from: {
+ email: "sender@mailtrap.io",
+ name: "Mailtrap",
+ },
+ subject: "Batch Subject",
+ text: "Batch Text",
+ },
+ requests: [
+ {
+ to: [
+ {
+ email: "recipient1.mock@email.com",
+ name: "recipient1",
+ },
+ ],
+ },
+ {
+ to: [
+ {
+ email: "recipient2.mock@email.com",
+ name: "recipient2",
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = await batchClient.batchSend(batchData);
+
+ expect(mock.history.post[0].url).toEqual(endpoint);
+ expect(mock.history.post[0].data).toEqual(
+ JSON.stringify({
+ base: batchData.base,
+ requests: batchData.requests.map((req) => ({
+ to: req.to,
+ })),
+ })
+ );
+ expect(result).toEqual(expectedBatchResponseData);
+ });
+
+ it("successfully sends a batch of emails with template", async () => {
+ const batchClient = new MailtrapClient({
+ token: "MY_API_TOKEN",
+ bulk: true,
+ });
+ const endpoint = `${BULK_ENDPOINT}/api/batch`;
+ const expectedBatchResponseData = {
+ success: true,
+ responses: [
+ {
+ success: true,
+ message_ids: ["57f17fe0-3935-11f0-0000-f15507b34198"],
+ },
+ ],
+ };
+ mock.onPost(endpoint).reply(200, expectedBatchResponseData);
+
+ const batchData = {
+ base: {
+ from: {
+ email: "sender@mailtrap.io",
+ name: "Mailtrap",
+ },
+ template_uuid: "813e39db-c74a-4830-b037-0e6ba8b1fe88",
+ template_variables: {
+ company_name: "Mailtrap",
+ },
+ },
+ requests: [
+ {
+ to: [
+ {
+ email: "recipient1.mock@email.com",
+ name: "recipient1",
+ },
+ ],
+ },
+ {
+ to: [
+ {
+ email: "recipient2.mock@email.com",
+ name: "recipient2",
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = await batchClient.batchSend(batchData);
+
+ expect(mock.history.post[0].url).toEqual(endpoint);
+ expect(mock.history.post[0].data).toEqual(
+ JSON.stringify({
+ base: batchData.base,
+ requests: batchData.requests.map((req) => ({
+ to: req.to,
+ })),
+ })
+ );
+ expect(result).toEqual(expectedBatchResponseData);
+ });
+
+ it("successfully sends a batch of transactional emails", async () => {
+ const batchClient = new MailtrapClient({
+ token: "MY_API_TOKEN",
+ });
+ const endpoint = `${SENDING_ENDPOINT}/api/batch`;
+ const expectedBatchResponseData = {
+ success: true,
+ responses: [
+ {
+ success: true,
+ message_ids: ["1cc512a0-3936-11f0-0040-f1a6f440b26b"], // First recipient
+ },
+ {
+ success: true,
+ message_ids: ["1cc47660-3936-11f0-0040-f1a6f440b26b"], // Second recipient
+ },
+ ],
+ };
+ mock.onPost(endpoint).reply(200, expectedBatchResponseData);
+
+ const batchData = {
+ base: {
+ from: {
+ email: "sender@mailtrap.io",
+ name: "Mailtrap",
+ },
+ subject: "Transactional Batch Subject",
+ text: "Transactional Batch Text",
+ },
+ requests: [
+ {
+ to: [
+ {
+ email: "recipient1.mock@email.com",
+ name: "recipient1",
+ },
+ ],
+ },
+ {
+ to: [
+ {
+ email: "recipient2.mock@email.com",
+ name: "recipient2",
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = await batchClient.batchSend(batchData);
+
+ expect(mock.history.post[0].url).toEqual(endpoint);
+ expect(mock.history.post[0].data).toEqual(
+ JSON.stringify({
+ base: batchData.base,
+ requests: batchData.requests.map((req) => ({
+ to: req.to,
+ })),
+ })
+ );
+ expect(result).toEqual(expectedBatchResponseData);
+ });
+
+ it("handles API errors for batch sending", async () => {
+ const batchClient = new MailtrapClient({
+ token: "MY_API_TOKEN",
+ bulk: true,
+ });
+ const endpoint = `${BULK_ENDPOINT}/api/batch`;
+ const responseData = {
+ success: true,
+ responses: [
+ {
+ success: false,
+ errors: ["'from' is required", "'subject' is required"],
+ },
+ ],
+ };
+ mock.onPost(endpoint).reply(200, responseData);
+
+ const batchData = {
+ base: {
+ text: "Batch Text",
+ },
+ requests: [
+ {
+ to: [
+ {
+ email: "recipient1.mock@email.com",
+ name: "recipient1",
+ },
+ ],
+ },
+ ],
+ };
+
+ // @ts-ignore
+ const result = await batchClient.batchSend(batchData);
+ expect(result).toEqual(responseData);
+ expect(result.responses[0].success).toBe(false);
+ expect(result.responses[0].errors).toContain("'from' is required");
+ expect(result.responses[0].errors).toContain("'subject' is required");
+ });
+ });
});
describe("get testing(): ", () => {
diff --git a/src/lib/MailtrapClient.ts b/src/lib/MailtrapClient.ts
index a0b9e81..eb4dcc6 100644
--- a/src/lib/MailtrapClient.ts
+++ b/src/lib/MailtrapClient.ts
@@ -11,7 +11,13 @@ import TestingAPI from "./api/Testing";
import CONFIG from "../config";
-import { Mail, SendResponse, MailtrapClientConfig } from "../types/mailtrap";
+import {
+ Mail,
+ SendResponse,
+ MailtrapClientConfig,
+ BatchSendResponse,
+ BatchSendRequest,
+} from "../types/mailtrap";
import MailtrapError from "./MailtrapError";
const { CLIENT_SETTINGS, ERRORS } = CONFIG;
@@ -129,4 +135,28 @@ export default class MailtrapClient {
return this.axios.post(url, preparedMail);
}
+
+ /**
+ * Sends a batch of emails with the given array of mail objects.
+ * If there is an error, rejects with MailtrapError.
+ */
+ public async batchSend(
+ request: BatchSendRequest
+ ): Promise {
+ const { requests, base } = request;
+ const host = this.determineHost();
+ const ifSandbox =
+ this.sandbox && this.testInboxId ? `/${this.testInboxId}` : "";
+ const url = `${host}/api/batch${ifSandbox}`;
+
+ const preparedBase = base ? encodeMailBuffers(base) : undefined;
+ const preparedRequests = requests.map((singleRequest) =>
+ encodeMailBuffers(singleRequest as Partial)
+ );
+
+ return this.axios.post(url, {
+ base: preparedBase,
+ requests: preparedRequests,
+ });
+ }
}
diff --git a/src/lib/mail-buffer-encoder.ts b/src/lib/mail-buffer-encoder.ts
index 1a33d06..1b4126a 100644
--- a/src/lib/mail-buffer-encoder.ts
+++ b/src/lib/mail-buffer-encoder.ts
@@ -5,7 +5,7 @@ import { Mail } from "../types/mailtrap";
* If `mail` has `html` and it's an instance of `Buffer`, then converts to string.
* If it has attachments, then converts `Buffer` content to `base64` string if needed.
*/
-export default function encodeMailBuffers(mail: Mail): Mail {
+export default function encodeMailBuffers(mail: Partial): Partial {
const preparedMail = { ...mail };
if ("text" in preparedMail && preparedMail.text instanceof Buffer) {
diff --git a/src/types/mailtrap.ts b/src/types/mailtrap.ts
index 6731ebd..4e8b506 100644
--- a/src/types/mailtrap.ts
+++ b/src/types/mailtrap.ts
@@ -68,3 +68,58 @@ export type MailtrapClientConfig = {
bulk?: boolean;
sandbox?: boolean;
};
+
+export type BatchMail = Mail[];
+
+export interface BatchSendResponse {
+ success: boolean;
+ responses: Array<{
+ success: boolean;
+ message_ids?: string[];
+ errors?: string[];
+ }>;
+}
+
+interface BaseAddress {
+ email: string;
+ name?: string;
+}
+
+interface InlineBatchSendBase extends Omit {
+ from: BaseAddress;
+ subject: string; // Required when using inline content
+ text?: string | Buffer;
+ html?: string | Buffer;
+ attachments?: Attachment[];
+ headers?: Record;
+ custom_variables?: Record;
+ category?: string; // Allowed for inline content
+ reply_to?: BaseAddress;
+}
+
+interface TemplateBatchSendBase extends Omit {
+ from: BaseAddress;
+ template_uuid: string; // Required for template usage
+ template_variables?: Record;
+ custom_variables?: Record;
+ reply_to?: BaseAddress;
+}
+
+export interface BatchSendRequest {
+ base?: InlineBatchSendBase | TemplateBatchSendBase;
+ requests: {
+ to: BaseAddress[];
+ cc?: BaseAddress[];
+ bcc?: BaseAddress[];
+ reply_to?: BaseAddress[];
+ subject?: string;
+ text?: string;
+ html?: string;
+ category?: string; // Only allowed when not using template_uuid
+ template_uuid?: string;
+ template_variables?: Record;
+ custom_variables?: Record;
+ attachments?: Attachment[];
+ headers?: Record;
+ }[];
+}