diff --git a/docs/use-cases/multiple-reply-to-email.md b/docs/use-cases/multiple-reply-to-email.md new file mode 100644 index 000000000..59736229d --- /dev/null +++ b/docs/use-cases/multiple-reply-to-email.md @@ -0,0 +1,24 @@ +# Multiple emails in replyTo + +An array of recipients who will receive replies and/or bounces. Each object in this array must contain the recipient's email address. Each object in the array may optionally contain the recipient's name. You can either choose to use “reply_to” field or “reply_to_list” but not both. [API specification](https://docs.sendgrid.com/api-reference/mail-send/mail-send#multiple-reply-to-emails) + +```js +const sgMail = require('@sendgrid/mail'); +sgMail.setApiKey(process.env.SENDGRID_API_KEY); +const msg = { + to: 'recipient@example.org', + from: 'sender@example.org', + subject: 'Multiple mail in replyTo', + html: '
Here’s an example of multiple replyTo email for you!
', + replyToList: [ + { + 'name': 'Test User1', + 'email': 'test_user1@example.org' + }, + { + 'email': 'test_user2@example.org' + } + ], +}; +``` + diff --git a/packages/helpers/classes/mail.d.ts b/packages/helpers/classes/mail.d.ts index f3246b804..65ba9329f 100644 --- a/packages/helpers/classes/mail.d.ts +++ b/packages/helpers/classes/mail.d.ts @@ -155,6 +155,8 @@ export interface MailData { dynamicTemplateData?: { [key: string]: any }, hideWarnings?: boolean, + + replyToList?: EmailJSON | EmailJSON[], } export type MailDataRequired = MailData & ( @@ -179,6 +181,7 @@ export interface MailJSON { batch_id?: string; template_id?: string; ip_pool_name?: string; + reply_to_list?: EmailJSON[]; } export default class Mail { @@ -353,4 +356,9 @@ export default class Mail { * Create a Mail instance from given data */ static create(data: MailData[]): Mail[]; + + /** + * Set reply_to_list header from given data + */ + setReplyToList(replyToList: EmailJSON[]): void; } diff --git a/packages/helpers/classes/mail.js b/packages/helpers/classes/mail.js index ac47128dc..b7dce14a2 100644 --- a/packages/helpers/classes/mail.js +++ b/packages/helpers/classes/mail.js @@ -68,7 +68,7 @@ class Mail { templateId, personalizations, attachments, ipPoolName, batchId, sections, headers, categories, category, customArgs, asm, mailSettings, trackingSettings, substitutions, substitutionWrappers, dynamicTemplateData, isMultiple, - hideWarnings, + hideWarnings, replyToList, } = data; //Set data @@ -90,6 +90,7 @@ class Mail { this.setMailSettings(mailSettings); this.setTrackingSettings(trackingSettings); this.setHideWarnings(hideWarnings); + this.setReplyToList(replyToList); if (this.isDynamic) { this.setDynamicTemplateData(dynamicTemplateData); @@ -504,7 +505,7 @@ class Mail { from, replyTo, sendAt, subject, content, templateId, personalizations, attachments, ipPoolName, batchId, asm, sections, headers, categories, customArgs, mailSettings, - trackingSettings, + trackingSettings, replyToList, } = this; //Initialize with mandatory values @@ -560,6 +561,9 @@ class Mail { if (typeof ipPoolName !== 'undefined') { json.ipPoolName = ipPoolName; } + if(typeof replyToList !== 'undefined') { + json.replyToList = replyToList; + } //Return as snake cased object return toSnakeCase(json, ['substitutions', 'dynamicTemplateData', 'customArgs', 'headers', 'sections']); @@ -667,6 +671,18 @@ class Mail { value, [this._checkUndefined, this._createCheckThatThrows(Array.isArray, 'Array expected for`' + propertyName + '`')]); } + + /** + * Set the replyToList from email body + */ + setReplyToList(replyToList) { + if (this._doArrayCheck('replyToList', replyToList) && replyToList.length) { + if (!replyToList.every(replyTo => replyTo && typeof replyTo.email === 'string')) { + throw new Error('Expected each replyTo to contain an `email` string'); + } + this.replyToList = replyToList; + } + } } //Export class diff --git a/packages/helpers/classes/mail.spec.js b/packages/helpers/classes/mail.spec.js index d62fb9969..41aec6be9 100644 --- a/packages/helpers/classes/mail.spec.js +++ b/packages/helpers/classes/mail.spec.js @@ -246,4 +246,62 @@ describe('Mail', function() { expect(logSpy.calledWith(DYNAMIC_TEMPLATE_CHAR_WARNING)).to.equal(true); }); }); + + describe('set replyToList to set multiple reply-to', () => { + let data; + + this.beforeEach(() => { + data = { + to: 'send-to@example.org', + from: 'sender@example.org', + subject: 'test replyToList', + category: 'test', + text: 'Testing replyToList settings', + html: 'Testing replyToList settings
', + }; + }); + + it('should set the replyToList', () => { + let replyToList = [ + { + 'name': 'Test User1', + 'email': 'test_user1@example.org' + }, + { + 'email': 'test_user2@example.org' + } + ]; + data.replyToList = replyToList; + + const mail = new Mail(data); + + expect(mail.replyToList) + .to.be.deep.equal(replyToList); + }); + + it('should throw error for incorrect replyToList format', () => { + let replyToList = [ + { + 'name': 'Test User1' + }, + { + 'email_data': 'test_user2@example.org' + } + ]; + data.replyToList = replyToList; + + expect(() => new Mail(data)) + .to.throw('Expected each replyTo to contain an `email` string'); + }); + + it('should throw error for as replyToList is not an array', () => { + let replyToList = { + 'name': 'Test User1', + 'email': 'test_user1@example.org' + }; + data.replyToList = replyToList; + expect(() => new Mail(data)) + .to.throw('Array expected for`replyToList`'); + }); + }); }); diff --git a/packages/mail/src/mail.spec.js b/packages/mail/src/mail.spec.js index 936b799a2..09698baa2 100644 --- a/packages/mail/src/mail.spec.js +++ b/packages/mail/src/mail.spec.js @@ -17,7 +17,7 @@ before(() => { * Default mock header */ beforeEach(() => { - sgClient.setDefaultHeader('X-Mock', 200); + sgClient.setDefaultHeader('X-Mock', 202); }); /** @@ -39,11 +39,11 @@ describe('sgMail.send()', () => { }); it('should send a basic email', () => { - sgClient.setDefaultHeader('X-Mock', 201); + sgClient.setDefaultHeader('X-Mock', 202); return sgMail .send(data) .then(([response, body]) => { - expect(response.statusCode).to.equal(201); + expect(response.statusCode).to.equal(202); }); }); @@ -54,12 +54,12 @@ describe('sgMail.send()', () => { }); it('should include custom headers to the request', () => { - sgClient.setDefaultHeader('X-Mock', 201); + sgClient.setDefaultHeader('X-Mock', 202); const clientSpy = sinon.spy(sgClient, "request") return sgMail .send(Object.assign(data, { headers: { customHeader: "Custom Header Content" } })) .then(([response, body]) => { - expect(response.statusCode).to.equal(201); + expect(response.statusCode).to.equal(202); expect(clientSpy).to.have.been.calledWith(sinon.match({ url: "/v3/mail/send", method: "POST", @@ -67,5 +67,70 @@ describe('sgMail.send()', () => { })); }); }); + + it('should send email with correct replyToList format', () => { + sgClient.setDefaultHeader('X-Mock', 202); + data["replyToList"] = [ + { + "name": "Test Team", + "email": "test@example.org" + }, + { + "name": "Support Test Team", + "email": "support.test@example.org" + } + ]; + return sgMail + .send(data) + .then(([response, body]) => { + expect(response.statusCode).to.equal(202); + }); + }); + + it('should throw error with wrong replyToList format', () => { + sgClient.setDefaultHeader('X-Mock', 202); + data["replyToList"] = { + "name": "Support Test Team", + "email": "support.test@example.org" + }; + return expect(function() { + sgMail.send(data, false, {}); + }).to.throw(Error); + }); + + it('should throw error if any record in replyToList is without email', () => { + data["replyToList"] = [ + { + "name": "Test Team", + "email": "test@example.org" + }, + { + "name": "Support Test Team" + } + ]; + return expect(function() { + sgMail.send(data, false, {}); + }).to.throw(Error); + }); + + it('should throw error if both replyTo and replyToList are mentioned', () => { + data["replyTo"] = { + "name": "Manual Tester", + "email": "manual.test@example.org" + }; + data["replyToList"] = [ + { + "name": "Test Team", + "email": "test@example.org" + }, + { + "name": "Support Test Team", + "email": "support.test@example.org" + } + ]; + return expect(function() { + sgMail.send(data, false, {}); + }).to.throw(Error); + }); });