diff --git a/packages/nodes-base/nodes/Google/Gmail/Gmail.node.ts b/packages/nodes-base/nodes/Google/Gmail/Gmail.node.ts index fb6eda49851..1ef33675594 100644 --- a/packages/nodes-base/nodes/Google/Gmail/Gmail.node.ts +++ b/packages/nodes-base/nodes/Google/Gmail/Gmail.node.ts @@ -13,13 +13,14 @@ export class Gmail extends VersionedNodeType { group: ['transform'], subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Consume the Gmail API', - defaultVersion: 2.1, + defaultVersion: 2.2, }; const nodeVersions: IVersionedNodeType['nodeVersions'] = { 1: new GmailV1(baseDescription), 2: new GmailV2(baseDescription), 2.1: new GmailV2(baseDescription), + 2.2: new GmailV2(baseDescription), }; super(nodeVersions, baseDescription); diff --git a/packages/nodes-base/nodes/Google/Gmail/test/utils/replyToEmail.test.ts b/packages/nodes-base/nodes/Google/Gmail/test/utils/replyToEmail.test.ts index c49577883c7..c66b8e5b072 100644 --- a/packages/nodes-base/nodes/Google/Gmail/test/utils/replyToEmail.test.ts +++ b/packages/nodes-base/nodes/Google/Gmail/test/utils/replyToEmail.test.ts @@ -87,7 +87,7 @@ describe('replyToEmail', () => { .mockResolvedValueOnce(mockSentMessage); // POST send message const options: IDataObject = {}; - const result = await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0); + const result = await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.2); expect(mockedGoogleApiRequest).toHaveBeenNthCalledWith( 1, @@ -139,7 +139,7 @@ describe('replyToEmail', () => { ccList: 'cc@example.com', }; - await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0); + await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.2); expect(mockedPrepareEmailsInput).toHaveBeenCalledWith('cc@example.com', 'CC', 0); @@ -164,7 +164,7 @@ describe('replyToEmail', () => { bccList: 'bcc@example.com', }; - await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0); + await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.2); expect(mockedPrepareEmailsInput).toHaveBeenCalledWith('bcc@example.com', 'BCC', 0); @@ -196,7 +196,7 @@ describe('replyToEmail', () => { }, }; - await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0); + await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.2); expect(mockedPrepareEmailAttachments).toHaveBeenCalledWith(options.attachmentsUi, 0); @@ -245,7 +245,7 @@ describe('replyToEmail', () => { replyToSenderOnly: true, }; - await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0); + await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.2); // Verify that only the sender is included in the "To" field expect(mockedEncodeEmail).toHaveBeenCalledWith( @@ -284,7 +284,7 @@ describe('replyToEmail', () => { replyToRecipientsOnly: true, }; - await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0); + await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.2); // Should filter out the current user's email from recipients and exclude sender expect(mockedEncodeEmail).toHaveBeenCalledWith( @@ -317,7 +317,7 @@ describe('replyToEmail', () => { senderName: 'Custom Sender Name', }; - await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0); + await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.2); expect(mockedEncodeEmail).toHaveBeenCalledWith( expect.objectContaining({ @@ -347,7 +347,7 @@ describe('replyToEmail', () => { const options: IDataObject = {}; - await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0); + await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.2); // Should handle both formats correctly expect(mockedGoogleApiRequest).toHaveBeenNthCalledWith( @@ -383,7 +383,7 @@ describe('replyToEmail', () => { const options: IDataObject = {}; - await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0); + await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.2); expect(mockedGoogleApiRequest).toHaveBeenCalledTimes(3); @@ -414,7 +414,7 @@ describe('replyToEmail', () => { const options: IDataObject = {}; - await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0); + await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.2); // Should handle missing subject gracefully (empty string) expect(mockedGoogleApiRequest).toHaveBeenCalledTimes(3); @@ -440,7 +440,7 @@ describe('replyToEmail', () => { const options: IDataObject = {}; - await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0); + await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.2); // Should handle missing message ID gracefully (empty string) expect(mockedGoogleApiRequest).toHaveBeenCalledTimes(3); @@ -459,7 +459,7 @@ describe('replyToEmail', () => { const options: IDataObject = {}; - await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0); + await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.2); expect(mockedPrepareEmailBody).toHaveBeenCalledWith(0); }); @@ -485,7 +485,7 @@ describe('replyToEmail', () => { .mockReturnValueOnce('cc@example.com, ') .mockReturnValueOnce('bcc@example.com, '); - await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0); + await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.2); expect(mockedEncodeEmail).toHaveBeenCalledWith({ from: 'Test Sender ', @@ -520,7 +520,7 @@ describe('replyToEmail', () => { const options: IDataObject = {}; - await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0); + await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.2); // Should handle missing headers with empty strings expect(mockedEncodeEmail).toHaveBeenCalledWith( @@ -554,7 +554,7 @@ describe('replyToEmail', () => { const options: IDataObject = {}; - await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0); + await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.2); // Should properly format emails with brackets expect(mockedEncodeEmail).toHaveBeenCalledWith( @@ -570,4 +570,62 @@ describe('replyToEmail', () => { }), ); }); + + test('should use ignore Reply-To header, when Reply-To header is provided for version < 2.2', async () => { + const messageWithReplyToHeader = { + ...mockMessageMetadata, + payload: { + ...mockMessageMetadata.payload, + headers: [ + ...mockMessageMetadata.payload.headers, + { name: 'Reply-To', value: 'reply-to@example.com' }, + ], + }, + }; + + mockedGoogleApiRequest + .mockResolvedValueOnce(messageWithReplyToHeader) + .mockResolvedValueOnce(mockUserProfile) + .mockResolvedValueOnce(mockSentMessage); + + const options: IDataObject = {}; + + await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.1); + + expect(mockedEncodeEmail).toHaveBeenCalledWith( + expect.objectContaining({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + to: expect.stringContaining(''), + }), + ); + }); + + test('should use Reply-To header instead of From, when Reply-To header is provided for version >= 2.2', async () => { + const messageWithReplyToHeader = { + ...mockMessageMetadata, + payload: { + ...mockMessageMetadata.payload, + headers: [ + ...mockMessageMetadata.payload.headers, + { name: 'Reply-To', value: 'reply-to@example.com' }, + ], + }, + }; + + mockedGoogleApiRequest + .mockResolvedValueOnce(messageWithReplyToHeader) + .mockResolvedValueOnce(mockUserProfile) + .mockResolvedValueOnce(mockSentMessage); + + const options: IDataObject = {}; + + await replyToEmail.call(mockExecuteFunctions, 'message123', options, 0, 2.2); + + expect(mockedEncodeEmail).toHaveBeenCalledWith( + expect.objectContaining({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + to: expect.stringContaining(''), + }), + ); + }); }); diff --git a/packages/nodes-base/nodes/Google/Gmail/utils/replyToEmail.ts b/packages/nodes-base/nodes/Google/Gmail/utils/replyToEmail.ts index 80865595ced..1d485ffa1c1 100644 --- a/packages/nodes-base/nodes/Google/Gmail/utils/replyToEmail.ts +++ b/packages/nodes-base/nodes/Google/Gmail/utils/replyToEmail.ts @@ -17,6 +17,7 @@ export async function replyToEmail( gmailId: string, options: IDataObject, itemIndex: number, + nodeVersion: number, ) { if (options.replyToSenderOnly && options.replyToRecipientsOnly) { throw new NodeOperationError( @@ -97,14 +98,19 @@ export async function replyToEmail( } }; + let replyToHeaderName = 'from'; + if (nodeVersion >= 2.2 && payload.headers.some((h) => h.name.toLowerCase() === 'reply-to')) { + replyToHeaderName = 'reply-to'; + } + for (const header of payload.headers) { const headerName = (header.name || '').toLowerCase(); - if (headerName === 'from' && !replyToRecipientsOnly) { - const from = header.value; - if (from.includes('<') && from.includes('>')) { - to.push(from); + if (headerName === replyToHeaderName && !replyToRecipientsOnly) { + const replyToEmail = header.value; + if (replyToEmail.includes('<') && replyToEmail.includes('>')) { + to.push(replyToEmail); } else { - to.push(`<${from}>`); + to.push(`<${replyToEmail}>`); } } diff --git a/packages/nodes-base/nodes/Google/Gmail/v2/GmailV2.node.ts b/packages/nodes-base/nodes/Google/Gmail/v2/GmailV2.node.ts index 450a7150268..677ec4622d6 100644 --- a/packages/nodes-base/nodes/Google/Gmail/v2/GmailV2.node.ts +++ b/packages/nodes-base/nodes/Google/Gmail/v2/GmailV2.node.ts @@ -59,7 +59,7 @@ const versionDescription: INodeTypeDescription = { name: 'gmail', icon: 'file:gmail.svg', group: ['transform'], - version: [2, 2.1], + version: [2, 2.1, 2.2], subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Consume the Gmail API', defaults: { @@ -353,7 +353,7 @@ export class GmailV2 implements INodeType { const messageIdGmail = this.getNodeParameter('messageId', i) as string; const options = this.getNodeParameter('options', i); - responseData = await replyToEmail.call(this, messageIdGmail, options, i); + responseData = await replyToEmail.call(this, messageIdGmail, options, i, nodeVersion); } if (operation === 'get') { //https://developers.google.com/gmail/api/v1/reference/users/messages/get @@ -777,7 +777,7 @@ export class GmailV2 implements INodeType { const messageIdGmail = this.getNodeParameter('messageId', i) as string; const options = this.getNodeParameter('options', i); - responseData = await replyToEmail.call(this, messageIdGmail, options, i); + responseData = await replyToEmail.call(this, messageIdGmail, options, i, nodeVersion); } if (operation === 'trash') { //https://developers.google.com/gmail/api/reference/rest/v1/users.threads/trash