Skip to content

Feature to add "reply_to" option when sending mails. #58

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

Merged
merged 7 commits into from
Apr 18, 2025
2 changes: 2 additions & 0 deletions examples/sending/everything.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { MailtrapClient } from "mailtrap"
const TOKEN = "<YOUR-TOKEN-HERE>";
const SENDER_EMAIL = "<[email protected]>";
const RECIPIENT_EMAIL = "<[email protected]>";
const REPLY_TO_EMAIL = "<[email protected]>";

const client = new MailtrapClient({ token: TOKEN });

Expand All @@ -28,6 +29,7 @@ client
from: { name: "Mailtrap Test", email: SENDER_EMAIL },
to: [{ email: RECIPIENT_EMAIL }],
subject: "Hello from Mailtrap!",
reply_to: { email: REPLY_TO_EMAIL },
html: `
<!doctype html>
<html>
Expand Down
2 changes: 2 additions & 0 deletions examples/sending/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { MailtrapTransport } from "mailtrap"
const TOKEN = "<YOUR-TOKEN-HERE>"
const SENDER_EMAIL = "<[email protected]>";
const RECIPIENT_EMAIL = "<[email protected]>";
const REPLY_TO_EMAIL = "<[email protected]>";

const transport = Nodemailer.createTransport(MailtrapTransport({
token: TOKEN,
Expand All @@ -26,6 +27,7 @@ transport.sendMail({
address: SENDER_EMAIL,
name: "Mailtrap Test"
},
replyTo: REPLY_TO_EMAIL,
subject: "Hello from Mailtrap!",
html: `
<!doctype html>
Expand Down
11 changes: 10 additions & 1 deletion src/__tests__/adapters/mail.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import adaptMail from "../../adapters/mail";

import config from "../../config";
import { adaptSingleRecipient } from "../../adapters/recipients";
import {
adaptSingleRecipient,
adaptReplyToRecipient,
} from "../../adapters/recipients";

const { ERRORS } = config;
const { SUBJECT_REQUIRED, FROM_REQUIRED } = ERRORS;
Expand All @@ -24,6 +27,7 @@ describe("adapters/mail: ", () => {
mockKey: "mock-value",
},
subject: "mock-subject",
replyTo: "mock-reply-to",
};

const expectedResult = {
Expand All @@ -33,6 +37,7 @@ describe("adapters/mail: ", () => {
bcc: [],
headers: data.headers,
subject: data.subject,
reply_to: adaptReplyToRecipient(data.replyTo),
};
const result = adaptMail(data);

Expand All @@ -47,6 +52,7 @@ describe("adapters/mail: ", () => {
mockKey: "mock-value",
},
attachments: [{ filename: "mock-filename", content: "mock-content" }],
replyTo: [],
};

const expectedResult = {
Expand All @@ -57,6 +63,7 @@ describe("adapters/mail: ", () => {
bcc: [],
headers: data.headers,
attachments: data.attachments,
reply_to: adaptReplyToRecipient(data.replyTo),
};
const result = adaptMail(data);

Expand All @@ -74,6 +81,7 @@ describe("adapters/mail: ", () => {
customVariables: {
user_id: "mock-user_id",
},
replyTo: ["mock-reply-to"],
};

const expectedResult = {
Expand All @@ -85,6 +93,7 @@ describe("adapters/mail: ", () => {
headers: data.headers,
attachments: data.attachments,
custom_variables: data.customVariables,
reply_to: adaptReplyToRecipient(data.replyTo),
};
const result = adaptMail(data);

Expand Down
57 changes: 57 additions & 0 deletions src/__tests__/adapters/recipients.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import adaptRecipients, {
adaptSingleRecipient,
adaptReplyToRecipient,
} from "../../adapters/recipients";

describe("adapters/recipients: ", () => {
Expand Down Expand Up @@ -85,4 +86,60 @@ describe("adapters/recipients: ", () => {
expect(result).toEqual(expectedResult);
});
});

describe("adaptReplyToRecipient(): ", () => {
it("returns undefined if recipients is invalid.", () => {
const recipients = undefined;

const expectedResult = undefined;
const result = adaptReplyToRecipient(recipients);

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

it("returns undefined if recipients is empty array.", () => {
const recipients: any = [];

const expectedResult = undefined;
const result = adaptReplyToRecipient(recipients);

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

it("returns adapted recipients if it's not an array.", () => {
const recipients = {
name: "mock-name",
address: "mock-email",
};

const expectedResult = {
name: recipients.name,
email: recipients.address,
};
const result = adaptReplyToRecipient(recipients);

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

it("returns first adapted recipient if it's an array.", () => {
const recipients = [
{
name: "mock-name-1",
address: "mock-email-1",
},
{
name: "mock-name-2",
address: "mock-email-2",
},
];

const expectedResult = {
name: recipients[0].name,
email: recipients[0].address,
};
const result = adaptReplyToRecipient(recipients);

expect(result).toEqual(expectedResult);
});
});
});
6 changes: 5 additions & 1 deletion src/adapters/mail.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import adaptAttachment from "./attachement";
import adaptContent from "./content";
import adaptHeaders from "./headers";
import adaptRecipients, { adaptSingleRecipient } from "./recipients";
import adaptRecipients, {
adaptSingleRecipient,
adaptReplyToRecipient,
} from "./recipients";

import CONFIG from "../config";

Expand All @@ -27,6 +30,7 @@ export default function adaptMail(data: MailtrapMailOptions): Mail | SendError {
to: adaptRecipients(data.to),
cc: adaptRecipients(data.cc),
bcc: adaptRecipients(data.bcc),
reply_to: adaptReplyToRecipient(data.replyTo),
};

if (data.headers) {
Expand Down
24 changes: 24 additions & 0 deletions src/adapters/recipients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,27 @@ export default function adaptRecipients(

return recipients.map(adaptSingleRecipient);
}

/**
* If there is no recipient or empty array is passed, then return undefined since it is an optional field.
* If it's not array, then adapt recipient and returns it.
* Otherwise, if type is array as nodemailer allows, we pick the first recipient
* as Mailtrap doesn't support multiple reply-to recipients.
*/
export function adaptReplyToRecipient(
recipients:
| string
| NodemailerAddress
| Array<string | NodemailerAddress>
| undefined
): Address | undefined {
if (!recipients || (Array.isArray(recipients) && recipients.length === 0)) {
return undefined;
}

if (!Array.isArray(recipients)) {
return adaptSingleRecipient(recipients);
}

return adaptSingleRecipient(recipients[0]);
}
1 change: 1 addition & 0 deletions src/types/mailtrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type CommonMail = {
attachments?: Attachment[];
headers?: MailtrapHeaders;
custom_variables?: CustomVariables;
reply_to?: Address;
};

export type TemplateVariables = Record<string, string | number | boolean>;
Expand Down