diff --git a/packages/nodes-base/credentials/GmailOAuth2Api.credentials.ts b/packages/nodes-base/credentials/GmailOAuth2Api.credentials.ts
new file mode 100644
index 0000000000..63fe6916f1
--- /dev/null
+++ b/packages/nodes-base/credentials/GmailOAuth2Api.credentials.ts
@@ -0,0 +1,30 @@
+import {
+ ICredentialType,
+ NodePropertyTypes,
+} from 'n8n-workflow';
+
+const scopes = [
+ 'https://www.googleapis.com/auth/gmail.labels',
+ 'https://www.googleapis.com/auth/gmail.addons.current.action.compose',
+ 'https://www.googleapis.com/auth/gmail.addons.current.message.action',
+ 'https://mail.google.com/',
+ 'https://www.googleapis.com/auth/gmail.modify',
+ 'https://www.googleapis.com/auth/gmail.compose',
+];
+
+
+export class GmailOAuth2Api implements ICredentialType {
+ name = 'gmailOAuth2';
+ extends = [
+ 'googleOAuth2Api',
+ ];
+ displayName = 'Gmail OAuth2 API';
+ properties = [
+ {
+ displayName: 'Scope',
+ name: 'scope',
+ type: 'hidden' as NodePropertyTypes,
+ default: scopes.join(' '),
+ },
+ ];
+}
diff --git a/packages/nodes-base/nodes/Google/Gmail/DraftDescription.ts b/packages/nodes-base/nodes/Google/Gmail/DraftDescription.ts
new file mode 100644
index 0000000000..0f80f90923
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Gmail/DraftDescription.ts
@@ -0,0 +1,382 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+export const draftOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'draft',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ description: 'Create a new email draft',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ description: 'Delete a draft',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Get a draft',
+ },
+ {
+ name: 'Get All',
+ value: 'getAll',
+ description: 'Get all drafts',
+ }
+ ],
+ default: 'create',
+ description: 'The operation to perform.',
+ },
+] as INodeProperties[];
+
+export const draftFields = [
+ {
+ displayName: 'Draft ID',
+ name: 'messageId',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'draft',
+ ],
+ operation: [
+ 'delete',
+ 'get',
+ ]
+ },
+ },
+ placeholder: 'r-3254521568507167962',
+ description: 'The ID of the draft to operate on.',
+ },
+ {
+ displayName: 'Subject',
+ name: 'subject',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'draft',
+ ],
+ operation: [
+ 'create',
+ ]
+ },
+ },
+ placeholder: 'Hello World!',
+ description: 'The message subject.',
+ },
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'draft',
+ ],
+ operation: [
+ 'create',
+ ]
+ },
+ },
+ placeholder: 'Hello World!',
+ description: 'The message body. This can be in HTML.',
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ displayOptions: {
+ show: {
+ resource: [
+ 'draft',
+ ],
+ operation: [
+ 'create',
+ ]
+ },
+ },
+ default: {},
+ options: [
+ {
+ displayName: 'To Email',
+ name: 'toList',
+ type: 'string',
+ default: [],
+ typeOptions: {
+ multipleValues: true,
+ multipleValueButtonText: 'Add To Email',
+ },
+ placeholder: 'info@example.com',
+ description: 'The email addresses of the recipients.',
+ },
+ {
+ displayName: 'CC Email',
+ name: 'ccList',
+ type: 'string',
+ description: 'The email addresses of the copy recipients.',
+ typeOptions: {
+ multipleValues: true,
+ multipleValueButtonText: 'Add CC Email',
+ },
+ placeholder: 'info@example.com',
+ default: [],
+ },
+ {
+ displayName: 'BCC Email',
+ name: 'bccList',
+ type: 'string',
+ description: 'The email addresses of the blind copy recipients.',
+ typeOptions: {
+ multipleValues: true,
+ multipleValueButtonText: 'Add BCC Email',
+ },
+ placeholder: 'info@example.com',
+ default: [],
+ },
+ {
+ displayName: 'Attachments',
+ name: 'attachmentsUi',
+ placeholder: 'Add Attachments',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: true,
+ },
+ options: [
+ {
+ name: 'attachmentsBinary',
+ displayName: 'Attachments Binary',
+ values: [
+ {
+ displayName: 'Property',
+ name: 'property',
+ type: 'string',
+ default: '',
+ description: 'Name of the binary property containing the data to be added to the email as an attachment',
+ },
+ ],
+ },
+ ],
+ default: '',
+ description: 'Array of supported attachments to add to the message.',
+ },
+ ],
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ displayOptions: {
+ show: {
+ resource: [
+ 'draft',
+ ],
+ operation: [
+ 'get',
+ ]
+ },
+ },
+ default: {},
+ options: [
+ {
+ displayName: 'Attachments Prefix',
+ name: 'dataPropertyAttachmentsPrefixName',
+ type: 'string',
+ default: 'attachment_',
+ displayOptions: {
+ hide: {
+ format: [
+ 'full',
+ 'metadata',
+ 'minimal',
+ 'raw',
+ ],
+ },
+ },
+ description: 'Prefix for name of the binary property to which to
write the attachments. An index starting with 0 will be added.
So if name is "attachment_" the first attachment is saved to "attachment_0"',
+ },
+ {
+ displayName: 'Format',
+ name: 'format',
+ type: 'options',
+ options: [
+ {
+ name: 'Full',
+ value: 'full',
+ description: 'Returns the full email message data with body content parsed in the payload field',
+ },
+ {
+ name: 'Metadata',
+ value: 'metadata',
+ description: 'Returns only email message ID, labels, and email headers.',
+ },
+ {
+ name: 'Minimal',
+ value: 'minimal',
+ description: 'Returns only email message ID and labels; does not return the email headers, body, or payload',
+ },
+ {
+ name: 'RAW',
+ value: 'raw',
+ description: 'Returns the full email message data with body content in the raw field as a base64url encoded string; the payload field is not used.'
+ },
+ {
+ name: 'Resolved',
+ value: 'resolved',
+ description: 'Returns the full email with all data resolved and attachments saved as binary data.',
+ },
+ ],
+ default: 'resolved',
+ description: 'The format to return the message in',
+ },
+ ]
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* draft:getAll */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'draft',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'draft',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 500,
+ },
+ default: 10,
+ description: 'How many results to return.',
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'draft',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Attachments Prefix',
+ name: 'dataPropertyAttachmentsPrefixName',
+ type: 'string',
+ default: 'attachment_',
+ displayOptions: {
+ hide: {
+ format: [
+ 'full',
+ 'ids',
+ 'metadata',
+ 'minimal',
+ 'raw',
+ ],
+ },
+ },
+ description: 'Prefix for name of the binary property to which to
write the attachments. An index starting with 0 will be added.
So if name is "attachment_" the first attachment is saved to "attachment_0"',
+ },
+ {
+ displayName: 'Format',
+ name: 'format',
+ type: 'options',
+ options: [
+ {
+ name: 'Full',
+ value: 'full',
+ description: 'Returns the full email message data with body content parsed in the payload field',
+ },
+ {
+ name: 'IDs',
+ value: 'ids',
+ description: 'Returns only the IDs of the emails',
+ },
+ {
+ name: 'Metadata',
+ value: 'metadata',
+ description: 'Returns only email message ID, labels, and email headers.',
+ },
+ {
+ name: 'Minimal',
+ value: 'minimal',
+ description: 'Returns only email message ID and labels; does not return the email headers, body, or payload',
+ },
+ {
+ name: 'RAW',
+ value: 'raw',
+ description: 'Returns the full email message data with body content in the raw field as a base64url encoded string; the payload field is not used.'
+ },
+ {
+ name: 'Resolved',
+ value: 'resolved',
+ description: 'Returns the full email with all data resolved and attachments saved as binary data.',
+ },
+ ],
+ default: 'resolved',
+ description: 'The format to return the message in',
+ },
+ {
+ displayName: 'Include Spam Trash',
+ name: 'includeSpamTrash',
+ type: 'boolean',
+ default: false,
+ description: 'Include messages from SPAM and TRASH in the results.',
+ },
+ ],
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/Google/Gmail/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Gmail/GenericFunctions.ts
new file mode 100644
index 0000000000..eee0111d2a
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Gmail/GenericFunctions.ts
@@ -0,0 +1,191 @@
+import {
+ OptionsWithUri,
+} from 'request';
+
+import {
+ simpleParser,
+ Source as ParserSource,
+} from 'mailparser';
+
+import {
+ IExecuteFunctions,
+ IExecuteSingleFunctions,
+ ILoadOptionsFunctions,
+} from 'n8n-core';
+
+import {
+ IBinaryKeyData,
+ IDataObject,
+ INodeExecutionData,
+} from 'n8n-workflow';
+
+import {
+ IEmail,
+} from './Gmail.node';
+
+export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string,
+ endpoint: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any
+ let options: OptionsWithUri = {
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ method,
+ body,
+ qs,
+ uri: uri || `https://www.googleapis.com${endpoint}`,
+ json: true,
+ };
+
+ options = Object.assign({}, options, option);
+
+ try {
+ if (Object.keys(body).length === 0) {
+ delete options.body;
+ }
+
+ //@ts-ignore
+ return await this.helpers.requestOAuth2.call(this, 'gmailOAuth2', options);
+
+ } catch (error) {
+ if (error.response && error.response.body && error.response.body.error) {
+
+ let errorMessages;
+
+ if (error.response.body.error.errors) {
+ // Try to return the error prettier
+ errorMessages = error.response.body.error.errors;
+
+ errorMessages = errorMessages.map((errorItem: IDataObject) => errorItem.message);
+
+ errorMessages = errorMessages.join('|');
+
+ } else if (error.response.body.error.message) {
+ errorMessages = error.response.body.error.message;
+ }
+
+ throw new Error(`Gmail error response [${error.statusCode}]: ${errorMessages}`);
+ }
+ throw error;
+ }
+}
+
+
+export async function parseRawEmail(this: IExecuteFunctions, messageEncoded: ParserSource, dataPropertyNameDownload: string): Promise {
+
+ const responseData = await simpleParser(messageEncoded);
+
+ const headers: IDataObject = {};
+ for (const header of responseData.headerLines) {
+ headers[header.key] = header.line;
+ }
+
+ // @ts-ignore
+ responseData.headers = headers;
+ // @ts-ignore
+ responseData.headerLines = undefined;
+
+ const binaryData: IBinaryKeyData = {};
+ if (responseData.attachments) {
+
+ for (let i = 0; i < responseData.attachments.length; i++) {
+ const attachment = responseData.attachments[i];
+ binaryData[`${dataPropertyNameDownload}${i}`] = await this.helpers.prepareBinaryData(attachment.content, attachment.filename, attachment.contentType);
+ }
+ // @ts-ignore
+ responseData.attachments = undefined;
+ }
+
+ return {
+ json: responseData as unknown as IDataObject,
+ binary: Object.keys(binaryData).length ? binaryData : undefined,
+ } as INodeExecutionData;
+}
+
+
+//------------------------------------------------------------------------------------------------------------------------------------------
+// This function converts an email object into a MIME encoded email and then converts that string into base64 encoding
+// for more info on MIME, https://docs.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/aa494197(v%3Dexchg.140)
+//------------------------------------------------------------------------------------------------------------------------------------------
+
+export function encodeEmail(email: IEmail) {
+ let mimeEmail = '';
+
+ if (email.attachments !== undefined && email.attachments !== []) {
+ const attachments = email.attachments.map((attachment) => {
+ return [
+ "--XXXXboundary text\n",
+ "Content-Type:", attachment.type, ";\n",
+ "Content-Transfer-Encoding: Base64\n",
+ "Content-Disposition: attachment;\n",
+ "\tfilename=\"", attachment.name, "\"\n\n",
+
+ attachment.content, "\n\n",
+
+ "--XXXXboundary text\n\n",
+ ].join('');
+ });
+
+ mimeEmail = [
+ "To: ", email.to, "\n",
+ "Cc: ", email.cc, "\n",
+ "Bcc: ", email.bcc, "\n",
+ "In-Reply-To: ", email.inReplyTo, "\n",
+ "References: ", email.reference, "\n",
+ "Subject: ", email.subject, "\n",
+ "MIME-Version: 1.0\n",
+ "Content-Type: multipart/mixed;\n",
+ "\tboundary=\"XXXXboundary text\"\n\n",
+
+ "This is a multipart message in MIME format.\n\n",
+
+ "--XXXXboundary text\n",
+ "Content-Type: text/plain\n\n",
+
+ email.body, "\n\n",
+
+ attachments.join(''),
+
+ "--XXXXboundary text--",
+ ].join('');
+ } else {
+ mimeEmail = [
+ "Content-Type: text/plain; charset=\"UTF-8\"\n",
+ "MIME-Version: 1.0\n",
+ "Content-Transfer-Encoding: 7bit\n",
+ "To: ", email.to, "\n",
+ "Cc: ", email.cc, "\n",
+ "Bcc: ", email.bcc, "\n",
+ "In-Reply-To: ", email.inReplyTo, "\n",
+ "References: ", email.reference, "\n",
+ "Subject: ", email.subject, "\n\n",
+ email.body,
+ ].join('');
+ }
+
+ return Buffer.from(mimeEmail).toString("base64").replace(/\+/g, '-').replace(/\//g, '_');
+}
+
+export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any
+
+ const returnData: IDataObject[] = [];
+
+ let responseData;
+ query.maxResults = 100;
+
+ do {
+ responseData = await googleApiRequest.call(this, method, endpoint, body, query);
+ query.pageToken = responseData['nextPageToken'];
+ returnData.push.apply(returnData, responseData[propertyName]);
+ } while (
+ responseData['nextPageToken'] !== undefined &&
+ responseData['nextPageToken'] !== ''
+ );
+
+ return returnData;
+}
+
+export function extractEmail(s: string) {
+ const data = s.split('<')[1];
+ return data.substring(0, data.length - 1);
+}
diff --git a/packages/nodes-base/nodes/Google/Gmail/Gmail.node.ts b/packages/nodes-base/nodes/Google/Gmail/Gmail.node.ts
new file mode 100644
index 0000000000..81645908bc
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Gmail/Gmail.node.ts
@@ -0,0 +1,755 @@
+import {
+ IExecuteFunctions,
+} from 'n8n-core';
+
+import {
+ IBinaryKeyData,
+ ILoadOptionsFunctions,
+ IDataObject,
+ INodeExecutionData,
+ INodePropertyOptions,
+ INodeType,
+ INodeTypeDescription,
+} from 'n8n-workflow';
+
+import {
+ encodeEmail,
+ extractEmail,
+ googleApiRequest,
+ googleApiRequestAllItems,
+ parseRawEmail,
+} from './GenericFunctions';
+
+import {
+ messageOperations,
+ messageFields,
+} from './MessageDescription';
+
+import {
+ messageLabelOperations,
+ messageLabelFields,
+} from './MessageLabelDescription';
+
+import {
+ labelOperations,
+ labelFields,
+} from './LabelDescription';
+
+import {
+ draftOperations,
+ draftFields,
+} from './DraftDescription';
+
+import {
+ isEmpty,
+} from 'lodash';
+
+export interface IEmail {
+ to?: string;
+ cc?: string;
+ bcc?: string;
+ inReplyTo?: string;
+ reference?: string;
+ subject: string;
+ body: string;
+ attachments?: IDataObject[];
+}
+
+interface IAttachments {
+ type: string;
+ name: string;
+ content: string;
+}
+
+export class Gmail implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'Gmail',
+ name: 'gmail',
+ icon: 'file:gmail.png',
+ group: ['transform'],
+ version: 1,
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
+ description: 'Consume the Gmail API',
+ defaults: {
+ name: 'Gmail',
+ color: '#d93025',
+ },
+ inputs: ['main'],
+ outputs: ['main'],
+ credentials: [
+ {
+ name: 'gmailOAuth2',
+ required: true,
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Resource',
+ name: 'resource',
+ type: 'options',
+ options: [
+ {
+ name: 'Draft',
+ value: 'draft',
+ },
+ {
+ name: 'Label',
+ value: 'label',
+ },
+ {
+ name: 'Message',
+ value: 'message',
+ },
+ {
+ name: 'Message Label',
+ value: 'messageLabel',
+ },
+ ],
+ default: 'draft',
+ description: 'The resource to operate on.',
+ },
+ //-------------------------------
+ // Draft Operations
+ //-------------------------------
+ ...draftOperations,
+ ...draftFields,
+ //-------------------------------
+ // Label Operations
+ //-------------------------------
+ ...labelOperations,
+ ...labelFields,
+ //-------------------------------
+ // Message Operations
+ //-------------------------------
+ ...messageOperations,
+ ...messageFields,
+ //-------------------------------
+ // MessageLabel Operations
+ //-------------------------------
+ ...messageLabelOperations,
+ ...messageLabelFields,
+ ],
+ };
+
+ methods = {
+ loadOptions: {
+ // Get all the labels to display them to user so that he can
+ // select them easily
+ async getLabels(
+ this: ILoadOptionsFunctions
+ ): Promise {
+ const returnData: INodePropertyOptions[] = [];
+ const labels = await googleApiRequestAllItems.call(
+ this,
+ 'labels',
+ 'GET',
+ '/gmail/v1/users/me/labels'
+ );
+ for (const label of labels) {
+ const labelName = label.name;
+ const labelId = label.id;
+ returnData.push({
+ name: labelName,
+ value: labelId
+ });
+ }
+ return returnData;
+ },
+ }
+ };
+
+ async execute(this: IExecuteFunctions): Promise {
+ const items = this.getInputData();
+ const returnData: IDataObject[] = [];
+ const resource = this.getNodeParameter('resource', 0) as string;
+ const operation = this.getNodeParameter('operation', 0) as string;
+
+ let method = '';
+ let body: IDataObject = {};
+ let qs: IDataObject = {};
+ let endpoint = '';
+ let responseData;
+
+ for (let i = 0; i < items.length; i++) {
+ if (resource === 'label') {
+ if (operation === 'create') {
+ //https://developers.google.com/gmail/api/v1/reference/users/labels/create
+ const labelName = this.getNodeParameter('name', i) as string;
+ const labelListVisibility = this.getNodeParameter('labelListVisibility', i) as string;
+ const messageListVisibility = this.getNodeParameter('messageListVisibility', i) as string;
+
+ method = 'POST';
+ endpoint = '/gmail/v1/users/me/labels';
+
+ body = {
+ labelListVisibility,
+ messageListVisibility,
+ name: labelName,
+ };
+
+ responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
+ }
+ if (operation === 'delete') {
+ //https://developers.google.com/gmail/api/v1/reference/users/labels/delete
+ const labelId = this.getNodeParameter('labelId', i) as string[];
+
+ method = 'DELETE';
+ endpoint = `/gmail/v1/users/me/labels/${labelId}`;
+ responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
+ responseData = { success: true };
+
+ }
+ if (operation === 'get') {
+ // https://developers.google.com/gmail/api/v1/reference/users/labels/get
+ const labelId = this.getNodeParameter('labelId', i);
+
+ method = 'GET';
+ endpoint = `/gmail/v1/users/me/labels/${labelId}`;
+
+ responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
+ }
+ if (operation === 'getAll') {
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+
+ responseData = await googleApiRequest.call(
+ this,
+ 'GET',
+ `/gmail/v1/users/me/labels`,
+ {},
+ qs
+ );
+
+ responseData = responseData.labels;
+
+ if (!returnAll) {
+ const limit = this.getNodeParameter('limit', i) as number;
+ responseData = responseData.splice(0, limit);
+ }
+ }
+ }
+ if (resource === 'messageLabel') {
+ if (operation === 'remove') {
+ //https://developers.google.com/gmail/api/v1/reference/users/messages/modify
+ const messageID = this.getNodeParameter('messageId', i);
+ const labelIds = this.getNodeParameter('labelIds', i) as string[];
+
+ method = 'POST';
+ endpoint = `/gmail/v1/users/me/messages/${messageID}/modify`;
+ body = {
+ removeLabelIds: labelIds,
+ };
+ responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
+ }
+ if (operation === 'add') {
+ // https://developers.google.com/gmail/api/v1/reference/users/messages/modify
+ const messageID = this.getNodeParameter('messageId', i);
+ const labelIds = this.getNodeParameter('labelIds', i) as string[];
+
+ method = 'POST';
+ endpoint = `/gmail/v1/users/me/messages/${messageID}/modify`;
+
+ body = {
+ addLabelIds: labelIds,
+ };
+
+ responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
+ }
+ }
+ if (resource === 'message') {
+ if (operation === 'send') {
+ // https://developers.google.com/gmail/api/v1/reference/users/messages/send
+
+ const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
+
+ let toStr = '';
+ let ccStr = '';
+ let bccStr = '';
+ let attachmentsList: IDataObject[] = [];
+
+ const toList = this.getNodeParameter('toList', i) as IDataObject[];
+
+ toList.forEach((email) => {
+ toStr += `<${email}>, `;
+ });
+
+ if (additionalFields.ccList) {
+ const ccList = additionalFields.ccList as IDataObject[];
+
+ ccList.forEach((email) => {
+ ccStr += `<${email}>, `;
+ });
+ }
+
+ if (additionalFields.bccList) {
+ const bccList = additionalFields.bccList as IDataObject[];
+
+ bccList.forEach((email) => {
+ bccStr += `<${email}>, `;
+ });
+ }
+
+ if (additionalFields.attachmentsUi) {
+ const attachmentsUi = additionalFields.attachmentsUi as IDataObject;
+ let attachmentsBinary = [];
+ if (!isEmpty(attachmentsUi)) {
+ if (attachmentsUi.hasOwnProperty('attachmentsBinary')
+ && !isEmpty(attachmentsUi.attachmentsBinary)
+ && items[i].binary) {
+ // @ts-ignore
+ attachmentsBinary = attachmentsUi.attachmentsBinary.map((value) => {
+ if (items[i].binary!.hasOwnProperty(value.property)) {
+ const aux: IAttachments = { name: '', content: '', type: '' };
+ aux.name = items[i].binary![value.property].fileName || 'unknown';
+ aux.content = items[i].binary![value.property].data;
+ aux.type = items[i].binary![value.property].mimeType;
+ return aux;
+ }
+ });
+ }
+
+ qs = {
+ userId: 'me',
+ uploadType: 'media',
+ };
+
+ attachmentsList = attachmentsBinary;
+ }
+ }
+
+ const email: IEmail = {
+ to: toStr,
+ cc: ccStr,
+ bcc: bccStr,
+ subject: this.getNodeParameter('subject', i) as string,
+ body: this.getNodeParameter('message', i) as string,
+ attachments: attachmentsList,
+ };
+
+ endpoint = '/gmail/v1/users/me/messages/send';
+ method = 'POST';
+
+ body = {
+ raw: encodeEmail(email),
+ };
+
+ responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
+ }
+ if (operation === 'reply') {
+
+ const id = this.getNodeParameter('messageId', i) as string;
+
+ const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
+
+ let toStr = '';
+ let ccStr = '';
+ let bccStr = '';
+ let attachmentsList: IDataObject[] = [];
+
+ const toList = this.getNodeParameter('toList', i) as IDataObject[];
+
+ toList.forEach((email) => {
+ toStr += `<${email}>, `;
+ });
+
+ if (additionalFields.ccList) {
+ const ccList = additionalFields.ccList as IDataObject[];
+
+ ccList.forEach((email) => {
+ ccStr += `<${email}>, `;
+ });
+ }
+
+ if (additionalFields.bccList) {
+ const bccList = additionalFields.bccList as IDataObject[];
+
+ bccList.forEach((email) => {
+ bccStr += `<${email}>, `;
+ });
+ }
+
+ if (additionalFields.attachmentsUi) {
+ const attachmentsUi = additionalFields.attachmentsUi as IDataObject;
+ let attachmentsBinary = [];
+ if (!isEmpty(attachmentsUi)) {
+ if (attachmentsUi.hasOwnProperty('attachmentsBinary')
+ && !isEmpty(attachmentsUi.attachmentsBinary)
+ && items[i].binary) {
+ // @ts-ignore
+ attachmentsBinary = attachmentsUi.attachmentsBinary.map((value) => {
+ if (items[i].binary!.hasOwnProperty(value.property)) {
+ const aux: IAttachments = { name: '', content: '', type: '' };
+ aux.name = items[i].binary![value.property].fileName || 'unknown';
+ aux.content = items[i].binary![value.property].data;
+ aux.type = items[i].binary![value.property].mimeType;
+ return aux;
+ }
+ });
+ }
+
+ qs = {
+ userId: 'me',
+ uploadType: 'media',
+ };
+
+ attachmentsList = attachmentsBinary;
+ }
+ }
+
+ // if no recipient is defined then grab the one who sent the email
+ if (toStr === '') {
+ endpoint = `/gmail/v1/users/me/messages/${id}`;
+
+ qs.format = 'metadata';
+
+ const { payload } = await googleApiRequest.call(this, method, endpoint, body, qs);
+
+ for (const header of payload.headers as IDataObject[]) {
+ if (header.name === 'From') {
+ toStr = `<${extractEmail(header.value as string)}>,`;
+ break;
+ }
+ }
+ }
+
+ const email: IEmail = {
+ to: toStr,
+ cc: ccStr,
+ bcc: bccStr,
+ subject: this.getNodeParameter('subject', i) as string,
+ body: this.getNodeParameter('message', i) as string,
+ attachments: attachmentsList,
+ };
+
+ endpoint = '/gmail/v1/users/me/messages/send';
+ method = 'POST';
+
+ email.inReplyTo = id;
+ email.reference = id;
+
+ body = {
+ raw: encodeEmail(email),
+ threadId: this.getNodeParameter('threadId', i) as string,
+ };
+
+ responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
+ }
+ if (operation === 'get') {
+ //https://developers.google.com/gmail/api/v1/reference/users/messages/get
+ method = 'GET';
+
+ const id = this.getNodeParameter('messageId', i);
+
+ const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
+ const format = additionalFields.format || 'resolved';
+
+ if (format === 'resolved') {
+ qs.format = 'raw';
+ } else {
+ qs.format = format;
+ }
+
+ endpoint = `/gmail/v1/users/me/messages/${id}`;
+
+ responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
+
+ let nodeExecutionData: INodeExecutionData;
+ if (format === 'resolved') {
+ const messageEncoded = Buffer.from(responseData.raw, 'base64').toString('utf8');
+ const dataPropertyNameDownload = additionalFields.dataPropertyAttachmentsPrefixName as string || 'attachment_';
+
+ nodeExecutionData = await parseRawEmail.call(this, messageEncoded, dataPropertyNameDownload);
+ } else {
+ nodeExecutionData = {
+ json: responseData,
+ };
+ }
+
+ responseData = nodeExecutionData;
+ }
+ if (operation === 'getAll') {
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+ const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
+ Object.assign(qs, additionalFields);
+
+ if (qs.labelIds) {
+ // tslint:disable-next-line: triple-equals
+ if (qs.labelIds == '') {
+ delete qs.labelIds;
+ } else {
+ qs.labelIds = (qs.labelIds as string[]).join(',');
+ }
+ }
+
+ if (returnAll) {
+ responseData = await googleApiRequestAllItems.call(
+ this,
+ 'messages',
+ 'GET',
+ `/gmail/v1/users/me/messages`,
+ {},
+ qs
+ );
+ } else {
+ qs.maxResults = this.getNodeParameter('limit', i) as number;
+ responseData = await googleApiRequest.call(
+ this,
+ 'GET',
+ `/gmail/v1/users/me/messages`,
+ {},
+ qs
+ );
+ responseData = responseData.messages;
+ }
+
+ if (responseData === undefined) {
+ responseData = [];
+ }
+
+ const format = additionalFields.format || 'resolved';
+
+ if (format !== 'ids') {
+
+ if (format === 'resolved') {
+ qs.format = 'raw';
+ } else {
+ qs.format = format;
+ }
+
+ for (let i = 0; i < responseData.length; i++) {
+ responseData[i] = await googleApiRequest.call(
+ this,
+ 'GET',
+ `/gmail/v1/users/me/messages/${responseData[i].id}`,
+ body,
+ qs
+ );
+
+ if (format === 'resolved') {
+ const messageEncoded = Buffer.from(responseData[i].raw, 'base64').toString('utf8');
+ const dataPropertyNameDownload = additionalFields.dataPropertyAttachmentsPrefixName as string || 'attachment_';
+ responseData[i] = await parseRawEmail.call(this, messageEncoded, dataPropertyNameDownload);
+ }
+ }
+ }
+
+ if (format !== 'resolved') {
+ responseData = this.helpers.returnJsonArray(responseData);
+ }
+
+ }
+ if (operation === 'delete') {
+ // https://developers.google.com/gmail/api/v1/reference/users/messages/delete
+ method = 'DELETE';
+ const id = this.getNodeParameter('messageId', i);
+
+ endpoint = `/gmail/v1/users/me/messages/${id}`;
+
+ responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
+
+ responseData = { success: true };
+ }
+ }
+ if (resource === 'draft') {
+ if (operation === 'create') {
+ // https://developers.google.com/gmail/api/v1/reference/users/drafts/create
+
+ const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
+
+ let toStr = '';
+ let ccStr = '';
+ let bccStr = '';
+ let attachmentsList: IDataObject[] = [];
+
+ if (additionalFields.toList) {
+ const toList = additionalFields.toList as IDataObject[];
+
+ toList.forEach((email) => {
+ toStr += `<${email}>, `;
+ });
+ }
+
+ if (additionalFields.ccList) {
+ const ccList = additionalFields.ccList as IDataObject[];
+
+ ccList.forEach((email) => {
+ ccStr += `<${email}>, `;
+ });
+ }
+
+ if (additionalFields.bccList) {
+ const bccList = additionalFields.bccList as IDataObject[];
+
+ bccList.forEach((email) => {
+ bccStr += `<${email}>, `;
+ });
+ }
+
+ if (additionalFields.attachmentsUi) {
+ const attachmentsUi = additionalFields.attachmentsUi as IDataObject;
+ let attachmentsBinary = [];
+ if (!isEmpty(attachmentsUi)) {
+ if (attachmentsUi.hasOwnProperty('attachmentsBinary')
+ && !isEmpty(attachmentsUi.attachmentsBinary)
+ && items[i].binary) {
+ // @ts-ignore
+ attachmentsBinary = attachmentsUi.attachmentsBinary.map((value) => {
+ if (items[i].binary!.hasOwnProperty(value.property)) {
+ const aux: IAttachments = { name: '', content: '', type: '' };
+ aux.name = items[i].binary![value.property].fileName || 'unknown';
+ aux.content = items[i].binary![value.property].data;
+ aux.type = items[i].binary![value.property].mimeType;
+ return aux;
+ }
+ });
+ }
+
+ qs = {
+ userId: 'me',
+ uploadType: 'media',
+ };
+
+ attachmentsList = attachmentsBinary;
+ }
+ }
+
+ const email: IEmail = {
+ to: toStr,
+ cc: ccStr,
+ bcc: bccStr,
+ subject: this.getNodeParameter('subject', i) as string,
+ body: this.getNodeParameter('message', i) as string,
+ attachments: attachmentsList,
+ };
+
+ endpoint = '/gmail/v1/users/me/drafts';
+ method = 'POST';
+
+ body = {
+ message: {
+ raw: encodeEmail(email),
+ },
+ };
+
+ responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
+ }
+ if (operation === 'get') {
+ // https://developers.google.com/gmail/api/v1/reference/users/drafts/get
+ method = 'GET';
+ const id = this.getNodeParameter('messageId', i);
+
+ const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
+ const format = additionalFields.format || 'resolved';
+
+ if (format === 'resolved') {
+ qs.format = 'raw';
+ } else {
+ qs.format = format;
+ }
+
+ endpoint = `/gmail/v1/users/me/drafts/${id}`;
+
+ responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
+
+ const binaryData: IBinaryKeyData = {};
+
+ let nodeExecutionData: INodeExecutionData;
+ if (format === 'resolved') {
+ const messageEncoded = Buffer.from(responseData.message.raw, 'base64').toString('utf8');
+ const dataPropertyNameDownload = additionalFields.dataPropertyAttachmentsPrefixName as string || 'attachment_';
+
+ nodeExecutionData = await parseRawEmail.call(this, messageEncoded, dataPropertyNameDownload);
+ } else {
+ nodeExecutionData = {
+ json: responseData,
+ binary: Object.keys(binaryData).length ? binaryData : undefined,
+ };
+ }
+
+ responseData = nodeExecutionData;
+ }
+ if (operation === 'delete') {
+ // https://developers.google.com/gmail/api/v1/reference/users/drafts/delete
+ method = 'DELETE';
+ const id = this.getNodeParameter('messageId', i);
+
+ endpoint = `/gmail/v1/users/me/drafts/${id}`;
+
+ responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
+
+ responseData = { success: true };
+ }
+ if (operation === 'getAll') {
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+ const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
+ Object.assign(qs, additionalFields);
+
+ if (returnAll) {
+ responseData = await googleApiRequestAllItems.call(
+ this,
+ 'drafts',
+ 'GET',
+ `/gmail/v1/users/me/drafts`,
+ {},
+ qs
+ );
+ } else {
+ qs.maxResults = this.getNodeParameter('limit', i) as number;
+ responseData = await googleApiRequest.call(
+ this,
+ 'GET',
+ `/gmail/v1/users/me/drafts`,
+ {},
+ qs
+ );
+ responseData = responseData.drafts;
+ }
+
+ if (responseData === undefined) {
+ responseData = [];
+ }
+
+ const format = additionalFields.format || 'resolved';
+
+ if (format !== 'ids') {
+ if (format === 'resolved') {
+ qs.format = 'raw';
+ } else {
+ qs.format = format;
+ }
+
+ for (let i = 0; i < responseData.length; i++) {
+
+ responseData[i] = await googleApiRequest.call(
+ this,
+ 'GET',
+ `/gmail/v1/users/me/drafts/${responseData[i].id}`,
+ body,
+ qs
+ );
+
+ if (format === 'resolved') {
+ const messageEncoded = Buffer.from(responseData[i].message.raw, 'base64').toString('utf8');
+ const dataPropertyNameDownload = additionalFields.dataPropertyAttachmentsPrefixName as string || 'attachment_';
+ responseData[i] = await parseRawEmail.call(this, messageEncoded, dataPropertyNameDownload);
+ }
+ }
+ }
+
+ if (format !== 'resolved') {
+ responseData = this.helpers.returnJsonArray(responseData);
+ }
+ }
+ }
+ if (Array.isArray(responseData)) {
+ returnData.push.apply(returnData, responseData as IDataObject[]);
+ } else {
+ returnData.push(responseData as IDataObject);
+ }
+ }
+ if (['draft', 'message'].includes(resource) && ['get', 'getAll'].includes(operation)) {
+ //@ts-ignore
+ return this.prepareOutputData(returnData);
+ }
+ return [this.helpers.returnJsonArray(returnData)];
+ }
+}
diff --git a/packages/nodes-base/nodes/Google/Gmail/LabelDescription.ts b/packages/nodes-base/nodes/Google/Gmail/LabelDescription.ts
new file mode 100644
index 0000000000..7fddc17e9b
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Gmail/LabelDescription.ts
@@ -0,0 +1,187 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+export const labelOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'label',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ description: 'Create a new label',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ description: 'Delete a label',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Get a label',
+ },
+ {
+ name: 'Get All',
+ value: 'getAll',
+ description: 'Get all labels',
+ }
+ ],
+ default: 'create',
+ description: 'The operation to perform',
+ },
+] as INodeProperties[];
+
+export const labelFields = [
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'label',
+ ],
+ operation: [
+ 'create',
+ ]
+ },
+ },
+ placeholder: 'invoices',
+ description: 'Label Name',
+ },
+ {
+ displayName: 'Label ID',
+ name: 'labelId',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'label',
+ ],
+ operation: [
+ 'get',
+ 'delete',
+ ],
+ },
+ },
+ description: 'The ID of the label',
+ },
+ {
+ displayName: 'Label List Visibility',
+ name: 'labelListVisibility',
+ type: 'options',
+ options: [
+ {
+ name: 'Hide',
+ value: 'labelHide',
+ },
+ {
+ name: 'Show',
+ value: 'labelShow',
+ },
+ {
+ name: 'Show If Unread',
+ value: 'labelShowIfUnread',
+ },
+ ],
+ default: 'labelShow',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'label',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'The visibility of the label in the label list in the Gmail web interface.',
+ },
+ {
+ displayName: 'Message List Visibility',
+ name: 'messageListVisibility',
+ type: 'options',
+ options: [
+ {
+ name: 'Hide',
+ value: 'hide',
+ },
+ {
+ name: 'show',
+ value: 'show',
+ },
+ ],
+ default: 'show',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'label',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'The visibility of messages with this label in the message list in the Gmail web interface.',
+ },
+ /* -------------------------------------------------------------------------- */
+ /* label:getAll */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'label',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'label',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 500,
+ },
+ default: 100,
+ description: 'How many results to return.',
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/Google/Gmail/MessageDescription.ts b/packages/nodes-base/nodes/Google/Gmail/MessageDescription.ts
new file mode 100644
index 0000000000..cd23aa0fb3
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Gmail/MessageDescription.ts
@@ -0,0 +1,464 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+export const messageOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'message',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Send',
+ value: 'send',
+ description: 'Send an email',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ description: 'Delete a message',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Get a message',
+ },
+ {
+ name: 'Get All',
+ value: 'getAll',
+ description: 'Get all messages',
+ },
+ {
+ name: 'Reply',
+ value: 'reply',
+ description: 'Reply to an email',
+ },
+ ],
+ default: 'send',
+ description: 'The operation to perform.',
+ },
+] as INodeProperties[];
+
+export const messageFields = [
+ {
+ displayName: 'Message ID',
+ name: 'messageId',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'message',
+ ],
+ operation: [
+ 'get',
+ 'delete',
+ ]
+ },
+ },
+ placeholder: '172ce2c4a72cc243',
+ description: 'The ID of the message you are operating on.',
+ },
+ {
+ displayName: 'Thread ID',
+ name: 'threadId',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'message',
+ ],
+ operation: [
+ 'reply',
+ ]
+ },
+ },
+ placeholder: '172ce2c4a72cc243',
+ description: 'The ID of the thread you are replying to.',
+ },
+ {
+ displayName: 'Message ID',
+ name: 'messageId',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'message',
+ ],
+ operation: [
+ 'reply',
+ ]
+ },
+ },
+ placeholder: 'CAHNQoFsC6JMMbOBJgtjsqN0eEc+gDg2a=SQj-tWUebQeHMDgqQ@mail.gmail.com',
+ description: 'The ID of the message you are replying to.',
+ },
+ {
+ displayName: 'Subject',
+ name: 'subject',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'message',
+ ],
+ operation: [
+ 'reply',
+ 'send',
+ ]
+ },
+ },
+ placeholder: 'Hello World!',
+ description: 'The message subject.',
+ },
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'message',
+ ],
+ operation: [
+ 'reply',
+ 'send',
+ ]
+ },
+ },
+ placeholder: 'Hello World!',
+ description: 'The message body. This can be in HTML.',
+ },
+ {
+ displayName: 'To Email',
+ name: 'toList',
+ type: 'string',
+ default: [],
+ required: true,
+ typeOptions: {
+ multipleValues: true,
+ multipleValueButtonText: 'Add To Email',
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'message',
+ ],
+ operation: [
+ 'reply',
+ 'send',
+ ]
+ },
+ },
+ placeholder: 'info@example.com',
+ description: 'The email addresses of the recipients.',
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ displayOptions: {
+ show: {
+ resource: [
+ 'message',
+ ],
+ operation: [
+ 'send',
+ 'reply',
+ ]
+ },
+ },
+ default: {},
+ options: [
+ {
+ displayName: 'CC Email',
+ name: 'ccList',
+ type: 'string',
+ description: 'The email addresses of the copy recipients.',
+ typeOptions: {
+ multipleValues: true,
+ multipleValueButtonText: 'Add CC Email',
+ },
+ placeholder: 'info@example.com',
+ default: [],
+ },
+ {
+ displayName: 'BCC Email',
+ name: 'bccList',
+ type: 'string',
+ description: 'The email addresses of the blind copy recipients.',
+ typeOptions: {
+ multipleValues: true,
+ multipleValueButtonText: 'Add BCC Email',
+ },
+ placeholder: 'info@example.com',
+ default: [],
+ },
+ {
+ displayName: 'Attachments',
+ name: 'attachmentsUi',
+ placeholder: 'Add Attachments',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: true,
+ },
+ options: [
+ {
+ name: 'attachmentsBinary',
+ displayName: 'Attachments Binary',
+ values: [
+ {
+ displayName: 'Property',
+ name: 'property',
+ type: 'string',
+ default: '',
+ description: 'Name of the binary properties which contain data which should be added to email as attachment',
+ },
+ ],
+ },
+ ],
+ default: '',
+ description: 'Array of supported attachments to add to the message.',
+ },
+ ]
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ displayOptions: {
+ show: {
+ resource: [
+ 'message',
+ ],
+ operation: [
+ 'get',
+ ]
+ },
+ },
+ default: {},
+ options: [
+ {
+ displayName: 'Format',
+ name: 'format',
+ type: 'options',
+ options: [
+ {
+ name: 'Full',
+ value: 'full',
+ description: 'Returns the full email message data with body content parsed in the payload field',
+ },
+ {
+ name: 'Metadata',
+ value: 'metadata',
+ description: 'Returns only email message ID, labels, and email headers.',
+ },
+ {
+ name: 'Minimal',
+ value: 'minimal',
+ description: 'Returns only email message ID and labels; does not return the email headers, body, or payload',
+ },
+ {
+ name: 'RAW',
+ value: 'raw',
+ description: 'Returns the full email message data with body content in the raw field as a base64url encoded string; the payload field is not used.'
+ },
+ {
+ name: 'Resolved',
+ value: 'resolved',
+ description: 'Returns the full email with all data resolved and attachments saved as binary data.',
+ },
+ ],
+ default: 'resolved',
+ description: 'The format to return the message in',
+ },
+ {
+ displayName: 'Attachments Prefix',
+ name: 'dataPropertyAttachmentsPrefixName',
+ type: 'string',
+ default: 'attachment_',
+ displayOptions: {
+ hide: {
+ format: [
+ 'full',
+ 'metadata',
+ 'minimal',
+ 'raw',
+ ],
+ },
+ },
+ description: 'Prefix for name of the binary property to which to
write the attachments. An index starting with 0 will be added.
So if name is "attachment_" the first attachment is saved to "attachment_0"',
+ },
+ ]
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* message:getAll */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'message',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'message',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 500,
+ },
+ default: 10,
+ description: 'How many results to return.',
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'message',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Attachments Prefix',
+ name: 'dataPropertyAttachmentsPrefixName',
+ type: 'string',
+ default: 'attachment_',
+ displayOptions: {
+ hide: {
+ format: [
+ 'full',
+ 'ids',
+ 'metadata',
+ 'minimal',
+ 'raw',
+ ],
+ },
+ },
+ description: 'Prefix for name of the binary property to which to
write the attachments. An index starting with 0 will be added.
So if name is "attachment_" the first attachment is saved to "attachment_0"',
+ },
+ {
+ displayName: 'Format',
+ name: 'format',
+ type: 'options',
+ options: [
+ {
+ name: 'Full',
+ value: 'full',
+ description: 'Returns the full email message data with body content parsed in the payload field',
+ },
+ {
+ name: 'IDs',
+ value: 'ids',
+ description: 'Returns only the IDs of the emails',
+ },
+ {
+ name: 'Metadata',
+ value: 'metadata',
+ description: 'Returns only email message ID, labels, and email headers.',
+ },
+ {
+ name: 'Minimal',
+ value: 'minimal',
+ description: 'Returns only email message ID and labels; does not return the email headers, body, or payload',
+ },
+ {
+ name: 'RAW',
+ value: 'raw',
+ description: 'Returns the full email message data with body content in the raw field as a base64url encoded string; the payload field is not used.'
+ },
+ {
+ name: 'Resolved',
+ value: 'resolved',
+ description: 'Returns the full email with all data resolved and attachments saved as binary data.',
+ },
+ ],
+ default: 'resolved',
+ description: 'The format to return the message in',
+ },
+ {
+ displayName: 'Include Spam Trash',
+ name: 'includeSpamTrash',
+ type: 'boolean',
+ default: false,
+ description: 'Include messages from SPAM and TRASH in the results.',
+ },
+ {
+ displayName: 'Label IDs',
+ name: 'labelIds',
+ type: 'multiOptions',
+ typeOptions: {
+ loadOptionsMethod: 'getLabels',
+ },
+ default: '',
+ description: 'Only return messages with labels that match all of the specified label IDs.',
+ },
+ {
+ displayName: 'Query',
+ name: 'q',
+ type: 'string',
+ typeOptions: {
+ alwaysOpenEditWindow: true,
+ },
+ default: '',
+ description: `Only return messages matching the specified query.
+ Supports the same query format as the Gmail search box.
+ For example, "from:someuser@example.com rfc822msgid: is:unread".
+ Parameter cannot be used when accessing the api using the gmail.metadata scope.`,
+ },
+ ],
+ },
+
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/Google/Gmail/MessageLabelDescription.ts b/packages/nodes-base/nodes/Google/Gmail/MessageLabelDescription.ts
new file mode 100644
index 0000000000..1215044ebe
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Gmail/MessageLabelDescription.ts
@@ -0,0 +1,77 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+export const messageLabelOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'messageLabel',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Add',
+ value: 'add',
+ description: 'Add a label to a message',
+ },
+ {
+ name: 'Remove',
+ value: 'remove',
+ description: 'Remove a label from a message',
+ },
+ ],
+ default: 'add',
+ description: 'The operation to perform',
+ },
+] as INodeProperties[];
+
+export const messageLabelFields = [
+ {
+ displayName: 'Message ID',
+ name: 'messageId',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'messageLabel',
+ ],
+ operation: [
+ 'add',
+ 'remove',
+ ]
+ },
+ },
+ placeholder: '172ce2c4a72cc243',
+ description: 'The message ID of your email.',
+ },
+ {
+ displayName: 'Label IDs',
+ name: 'labelIds',
+ type: 'multiOptions',
+ typeOptions: {
+ loadOptionsMethod: 'getLabels',
+ },
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'messageLabel',
+ ],
+ operation: [
+ 'add',
+ 'remove',
+ ]
+ },
+ },
+ description: 'The ID of the label',
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/Google/Gmail/gmail.png b/packages/nodes-base/nodes/Google/Gmail/gmail.png
new file mode 100644
index 0000000000..cb9198dd2e
Binary files /dev/null and b/packages/nodes-base/nodes/Google/Gmail/gmail.png differ
diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json
index 65264af889..f4f650bfc3 100644
--- a/packages/nodes-base/package.json
+++ b/packages/nodes-base/package.json
@@ -67,6 +67,7 @@
"dist/credentials/GithubOAuth2Api.credentials.js",
"dist/credentials/GitlabApi.credentials.js",
"dist/credentials/GitlabOAuth2Api.credentials.js",
+ "dist/credentials/GmailOAuth2Api.credentials.js",
"dist/credentials/GoogleApi.credentials.js",
"dist/credentials/GoogleCalendarOAuth2Api.credentials.js",
"dist/credentials/GoogleContactsOAuth2Api.credentials.js",
@@ -235,6 +236,7 @@
"dist/nodes/Google/Calendar/GoogleCalendar.node.js",
"dist/nodes/Google/Contacts/GoogleContacts.node.js",
"dist/nodes/Google/Drive/GoogleDrive.node.js",
+ "dist/nodes/Google/Gmail/Gmail.node.js",
"dist/nodes/Google/Sheet/GoogleSheets.node.js",
"dist/nodes/Google/Task/GoogleTasks.node.js",
"dist/nodes/Google/YouTube/YouTube.node.js",
@@ -360,6 +362,7 @@
"@types/imap-simple": "^4.2.0",
"@types/jest": "^25.2.1",
"@types/lodash.set": "^4.3.6",
+ "@types/mailparser": "^2.7.3",
"@types/moment-timezone": "^0.5.12",
"@types/mongodb": "^3.5.4",
"@types/mssql": "^6.0.2",
@@ -394,6 +397,7 @@
"lodash.get": "^4.4.2",
"lodash.set": "^4.3.2",
"lodash.unset": "^4.5.2",
+ "mailparser": "^2.8.1",
"moment": "2.24.0",
"moment-timezone": "^0.5.28",
"mongodb": "^3.5.5",