first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,31 @@
import type { IExecuteFunctions, INodeProperties } from 'n8n-workflow';
import { updateDisplayOptions } from '@utils/utilities';
import { messageRLC } from '../../descriptions';
import { microsoftApiRequest } from '../../transport';
export const properties: INodeProperties[] = [messageRLC];
const displayOptions = {
show: {
resource: ['message'],
operation: ['delete'],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(this: IExecuteFunctions, index: number) {
const messageId = this.getNodeParameter('messageId', index, undefined, {
extractValue: true,
}) as string;
await microsoftApiRequest.call(this, 'DELETE', `/messages/${messageId}`);
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray({ success: true }),
{ itemData: { item: index } },
);
return executionData;
}
@@ -0,0 +1,183 @@
import type {
IBinaryKeyData,
IDataObject,
IExecuteFunctions,
INodeExecutionData,
INodeProperties,
} from 'n8n-workflow';
import { updateDisplayOptions } from '@utils/utilities';
import { messageRLC } from '../../descriptions';
import { messageFields, simplifyOutputMessages } from '../../helpers/utils';
import { downloadAttachments, getMimeContent, microsoftApiRequest } from '../../transport';
export const properties: INodeProperties[] = [
messageRLC,
{
displayName: 'Output',
name: 'output',
type: 'options',
default: 'simple',
options: [
{
name: 'Simplified',
value: 'simple',
},
{
name: 'Raw',
value: 'raw',
},
{
name: 'Select Included Fields',
value: 'fields',
},
],
},
{
displayName: 'Fields',
name: 'fields',
type: 'multiOptions',
description: 'The fields to add to the output',
displayOptions: {
show: {
output: ['fields'],
},
},
options: messageFields,
default: [],
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add option',
default: {},
options: [
{
displayName: 'Attachments Prefix',
name: 'attachmentsPrefix',
type: 'string',
default: 'attachment_',
description:
'Prefix for name of the output fields to put the binary files data in. An index starting from 0 will be added. So if name is "attachment_" the first attachment is saved to "attachment_0".',
},
{
displayName: 'Download Attachments',
name: 'downloadAttachments',
type: 'boolean',
default: false,
description:
"Whether the message's attachments will be downloaded and included in the output",
},
{
displayName: 'Get MIME Content',
name: 'getMimeContent',
type: 'fixedCollection',
default: { values: { binaryPropertyName: 'data' } },
options: [
{
displayName: 'Values',
name: 'values',
values: [
{
displayName: 'Put Output in Field',
name: 'binaryPropertyName',
type: 'string',
default: '',
hint: 'The name of the output field to put the binary file data in',
},
{
displayName: 'File Name',
name: 'outputFileName',
type: 'string',
placeholder: 'message',
default: '',
description: 'Optional name of the output file, if not set message ID is used',
},
],
},
],
},
],
},
];
const displayOptions = {
show: {
resource: ['message'],
operation: ['get'],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(this: IExecuteFunctions, index: number) {
let responseData;
const qs: IDataObject = {};
const messageId = this.getNodeParameter('messageId', index, undefined, {
extractValue: true,
}) as string;
const options = this.getNodeParameter('options', index, {});
const output = this.getNodeParameter('output', index) as string;
if (output === 'fields') {
const fields = this.getNodeParameter('fields', index) as string[];
if (options.downloadAttachments) {
fields.push('hasAttachments');
}
qs.$select = fields.join(',');
}
if (output === 'simple') {
qs.$select =
'id,conversationId,subject,bodyPreview,from,toRecipients,categories,hasAttachments';
}
responseData = await microsoftApiRequest.call(
this,
'GET',
`/messages/${messageId}`,
undefined,
qs,
);
if (output === 'simple') {
responseData = simplifyOutputMessages([responseData as IDataObject]);
}
let executionData: INodeExecutionData[] = [];
if (options.downloadAttachments) {
const prefix = (options.attachmentsPrefix as string) || 'attachment_';
executionData = await downloadAttachments.call(this, responseData as IDataObject, prefix);
} else {
executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: index } },
);
}
if (options.getMimeContent) {
const { binaryPropertyName, outputFileName } = (options.getMimeContent as IDataObject)
.values as IDataObject;
const binary = await getMimeContent.call(
this,
messageId,
binaryPropertyName as string,
outputFileName as string,
);
executionData[0].binary = {
...(executionData[0].binary || {}),
...(binary as IBinaryKeyData),
};
}
return executionData;
}
@@ -0,0 +1,313 @@
import type {
IDataObject,
IExecuteFunctions,
INodeExecutionData,
INodeProperties,
} from 'n8n-workflow';
import { updateDisplayOptions } from '@utils/utilities';
import { returnAllOrLimit } from '../../descriptions';
import { messageFields, prepareFilterString, simplifyOutputMessages } from '../../helpers/utils';
import {
downloadAttachments,
microsoftApiRequest,
microsoftApiRequestAllItems,
} from '../../transport';
export const properties: INodeProperties[] = [
...returnAllOrLimit,
{
displayName: 'Output',
name: 'output',
type: 'options',
default: 'simple',
options: [
{
name: 'Simplified',
value: 'simple',
},
{
name: 'Raw',
value: 'raw',
},
{
name: 'Select Included Fields',
value: 'fields',
},
],
},
{
displayName: 'Fields',
name: 'fields',
type: 'multiOptions',
description: 'The fields to add to the output',
displayOptions: {
show: {
output: ['fields'],
},
},
options: messageFields,
default: [],
},
{
displayName:
'Fetching a lot of messages may take a long time. Consider using filters to speed things up',
name: 'filtersNotice',
type: 'notice',
default: '',
displayOptions: {
show: {
returnAll: [true],
},
},
},
{
displayName: 'Filters',
name: 'filtersUI',
type: 'fixedCollection',
placeholder: 'Add Filters',
default: {},
options: [
{
displayName: 'Values',
name: 'values',
values: [
{
displayName: 'Filter By',
name: 'filterBy',
type: 'options',
options: [
{
name: 'Filters',
value: 'filters',
},
{
name: 'Search',
value: 'search',
},
],
default: 'filters',
},
{
displayName: 'Search',
name: 'search',
type: 'string',
default: '',
placeholder: 'e.g. automation',
description:
'Only return messages that contains search term. Without specific message properties, the search is carried out on the default search properties of from, subject, and body. <a href="https://docs.microsoft.com/en-us/graph/query-parameters#search-parameter target="_blank">More info</a>.',
displayOptions: {
show: {
filterBy: ['search'],
},
},
},
{
displayName: 'Filters',
name: 'filters',
type: 'collection',
placeholder: 'Add Filter',
default: {},
displayOptions: {
show: {
filterBy: ['filters'],
},
},
options: [
{
displayName: 'Filter Query',
name: 'custom',
type: 'string',
default: '',
placeholder: 'e.g. isRead eq false',
hint: 'Search query to filter messages. <a href="https://learn.microsoft.com/en-us/graph/filter-query-parameter">More info</a>.',
},
{
displayName: 'Has Attachments',
name: 'hasAttachments',
type: 'boolean',
default: false,
},
{
displayName: 'Folders to Exclude',
name: 'foldersToExclude',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getFolders',
},
default: [],
description:
'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
},
{
displayName: 'Folders to Include',
name: 'foldersToInclude',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getFolders',
},
default: [],
description:
'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
},
{
displayName: 'Read Status',
name: 'readStatus',
type: 'options',
default: 'unread',
hint: 'Filter messages by whether they have been read or not',
options: [
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
name: 'Unread and read messages',
value: 'both',
},
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
name: 'Unread messages only',
value: 'unread',
},
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
name: 'Read messages only',
value: 'read',
},
],
},
{
displayName: 'Received After',
name: 'receivedAfter',
type: 'dateTime',
default: '',
description:
'Get all messages received after the specified date. In an expression you can set date using string in ISO format or a timestamp in miliseconds.',
},
{
displayName: 'Received Before',
name: 'receivedBefore',
type: 'dateTime',
default: '',
description:
'Get all messages received before the specified date. In an expression you can set date using string in ISO format or a timestamp in miliseconds.',
},
{
displayName: 'Sender',
name: 'sender',
type: 'string',
default: '',
description: 'Sender name or email to filter by',
},
],
},
],
},
],
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add option',
default: {},
options: [
{
displayName: 'Attachments Prefix',
name: 'attachmentsPrefix',
type: 'string',
default: 'attachment_',
description:
'Prefix for name of the output fields to put the binary files data in. An index starting from 0 will be added. So if name is "attachment_" the first attachment is saved to "attachment_0".',
},
{
displayName: 'Download Attachments',
name: 'downloadAttachments',
type: 'boolean',
default: false,
description:
"Whether the message's attachments will be downloaded and included in the output",
},
],
},
];
const displayOptions = {
show: {
resource: ['message'],
operation: ['getAll'],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(this: IExecuteFunctions, index: number) {
let responseData;
const qs = {} as IDataObject;
const returnAll = this.getNodeParameter('returnAll', index);
const filters = this.getNodeParameter('filtersUI.values', index, {}) as IDataObject;
const options = this.getNodeParameter('options', index, {});
const output = this.getNodeParameter('output', index) as string;
if (output === 'fields') {
const fields = this.getNodeParameter('fields', index) as string[];
if (options.downloadAttachments) {
fields.push('hasAttachments');
}
qs.$select = fields.join(',');
}
if (output === 'simple') {
qs.$select =
'id,conversationId,subject,bodyPreview,from,toRecipients,categories,hasAttachments';
}
if (filters.filterBy === 'search' && filters.search !== '') {
qs.$search = `"${filters.search}"`;
}
if (filters.filterBy === 'filters') {
const filterString = prepareFilterString(filters);
if (filterString) {
qs.$filter = filterString;
}
}
const endpoint = '/messages';
if (returnAll) {
responseData = await microsoftApiRequestAllItems.call(
this,
'value',
'GET',
endpoint,
undefined,
qs,
);
} else {
qs.$top = this.getNodeParameter('limit', index);
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, undefined, qs);
responseData = responseData.value;
}
if (output === 'simple') {
responseData = simplifyOutputMessages(responseData as IDataObject[]);
}
let executionData: INodeExecutionData[] = [];
if (options.downloadAttachments) {
const prefix = (options.attachmentsPrefix as string) || 'attachment_';
executionData = await downloadAttachments.call(this, responseData as IDataObject, prefix);
} else {
executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject[]),
{ itemData: { item: index } },
);
}
return executionData;
}
@@ -0,0 +1,86 @@
import { SEND_AND_WAIT_OPERATION, type INodeProperties } from 'n8n-workflow';
import * as del from './delete.operation';
import * as get from './get.operation';
import * as getAll from './getAll.operation';
import * as move from './move.operation';
import * as reply from './reply.operation';
import * as send from './send.operation';
import * as sendAndWait from './sendAndWait.operation';
import * as update from './update.operation';
export { del as delete, get, getAll, move, reply, send, sendAndWait, update };
export const description: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['message'],
},
},
options: [
{
name: 'Delete',
value: 'delete',
description: 'Delete a message',
action: 'Delete a message',
},
{
name: 'Get',
value: 'get',
description: 'Retrieve a single message',
action: 'Get a message',
},
{
name: 'Get Many',
value: 'getAll',
description: 'List and search messages',
action: 'Get many messages',
},
{
name: 'Move',
value: 'move',
description: 'Move a message to a folder',
action: 'Move a message',
},
{
name: 'Reply',
value: 'reply',
description: 'Create a reply to a message',
action: 'Reply to a message',
},
{
name: 'Send',
value: 'send',
description: 'Send a message',
action: 'Send a message',
},
{
name: 'Send and Wait for Response',
value: SEND_AND_WAIT_OPERATION,
description: 'Send a message and wait for response',
action: 'Send message and wait for response',
},
{
name: 'Update',
value: 'update',
description: 'Update a message',
action: 'Update a message',
},
],
default: 'send',
},
...del.description,
...get.description,
...getAll.description,
...move.description,
...reply.description,
...send.description,
...sendAndWait.description,
...update.description,
];
@@ -0,0 +1,43 @@
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
import { updateDisplayOptions } from '@utils/utilities';
import { folderRLC, messageRLC } from '../../descriptions';
import { microsoftApiRequest } from '../../transport';
export const properties: INodeProperties[] = [
messageRLC,
{ ...folderRLC, displayName: 'Parent Folder' },
];
const displayOptions = {
show: {
resource: ['message'],
operation: ['move'],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(this: IExecuteFunctions, index: number) {
const messageId = this.getNodeParameter('messageId', index, undefined, {
extractValue: true,
}) as string;
const destinationId = this.getNodeParameter('folderId', index, undefined, {
extractValue: true,
}) as string;
const body: IDataObject = {
destinationId,
};
await microsoftApiRequest.call(this, 'POST', `/messages/${messageId}/move`, body);
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray({ success: true }),
{ itemData: { item: index } },
);
return executionData;
}
@@ -0,0 +1,302 @@
import type {
IDataObject,
IExecuteFunctions,
INodeExecutionData,
INodeProperties,
} from 'n8n-workflow';
import { updateDisplayOptions } from '@utils/utilities';
import { messageRLC } from '../../descriptions';
import { createMessage } from '../../helpers/utils';
import { microsoftApiRequest } from '../../transport';
export const properties: INodeProperties[] = [
messageRLC,
{
displayName: 'Reply to Sender Only',
name: 'replyToSenderOnly',
type: 'boolean',
default: false,
description: 'Whether to reply to the sender only or to the entire list of recipients',
},
{
displayName: 'Message',
name: 'message',
// name: 'bodyContent',
description: 'Message body content',
type: 'string',
typeOptions: {
rows: 2,
},
default: '',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
replyToSenderOnly: [true],
},
},
options: [
{
displayName: 'Attachments',
name: 'attachments',
type: 'fixedCollection',
placeholder: 'Add Attachment',
default: {},
typeOptions: {
multipleValues: true,
},
options: [
{
name: 'attachments',
displayName: 'Attachment',
values: [
{
displayName: 'Input Data Field Name',
name: 'binaryPropertyName',
type: 'string',
default: '',
placeholder: 'e.g. data',
hint: 'The name of the input field containing the binary file data to be attached',
},
],
},
],
},
{
displayName: 'BCC Recipients',
name: 'bccRecipients',
description: 'Comma-separated list of email addresses of BCC recipients',
type: 'string',
default: '',
},
{
displayName: 'CC Recipients',
name: 'ccRecipients',
description: 'Comma-separated list of email addresses of CC recipients',
type: 'string',
default: '',
},
{
displayName: 'Custom Headers',
name: 'internetMessageHeaders',
placeholder: 'Add Header',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
options: [
{
name: 'headers',
displayName: 'Header',
values: [
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description: 'Name of the header',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'Value to set for the header',
},
],
},
],
},
{
displayName: 'From',
name: 'from',
description:
'The owner of the mailbox from which the message is sent. Must correspond to the actual mailbox used.',
type: 'string',
default: '',
},
{
displayName: 'Importance',
name: 'importance',
description: 'The importance of the message',
type: 'options',
options: [
{
name: 'Low',
value: 'Low',
},
{
name: 'Normal',
value: 'Normal',
},
{
name: 'High',
value: 'High',
},
],
default: 'Normal',
},
{
displayName: 'Message Type',
name: 'bodyContentType',
description: 'Message body content type',
type: 'options',
options: [
{
name: 'HTML',
value: 'html',
},
{
name: 'Text',
value: 'Text',
},
],
default: 'html',
},
{
displayName: 'Read Receipt Requested',
name: 'isReadReceiptRequested',
description: 'Whether a read receipt is requested for the message',
type: 'boolean',
default: false,
},
{
displayName: 'To',
name: 'toRecipients',
description: 'Comma-separated list of email addresses of recipients',
type: 'string',
default: '',
},
{
displayName: 'Reply To',
name: 'replyTo',
description: 'Email address to use when replying',
type: 'string',
default: '',
},
{
displayName: 'Subject',
name: 'subject',
description: 'The subject of the message',
type: 'string',
default: '',
},
],
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Field',
default: {},
options: [
{
displayName: 'Save as Draft',
name: 'saveAsDraft',
description:
'Whether to save the message as a draft. If false, the message is sent immediately.',
type: 'boolean',
default: false,
},
],
},
];
const displayOptions = {
show: {
resource: ['message'],
operation: ['reply'],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(this: IExecuteFunctions, index: number, _: INodeExecutionData[]) {
const messageId = this.getNodeParameter('messageId', index, undefined, {
extractValue: true,
}) as string;
const replyToSenderOnly = this.getNodeParameter('replyToSenderOnly', index, false) as string;
const message = this.getNodeParameter('message', index) as string;
const saveAsDraft = this.getNodeParameter('options.saveAsDraft', index, false) as boolean;
const additionalFields = this.getNodeParameter('additionalFields', index, {});
const body: IDataObject = {};
let action = 'createReply';
if (!replyToSenderOnly) {
body.comment = message;
action = 'createReplyAll';
} else {
// body.comment = comment;
body.message = {} as IDataObject;
additionalFields.bodyContent = message;
Object.assign(body.message, createMessage(additionalFields));
delete (body.message as IDataObject).attachments;
}
const responseData = await microsoftApiRequest.call(
this,
'POST',
`/messages/${messageId}/${action}`,
body,
);
if (additionalFields.attachments) {
const attachments = (additionalFields.attachments as IDataObject).attachments as IDataObject[];
// // Handle attachments
const data: IDataObject[] = [];
for (const attachment of attachments) {
const binaryPropertyName = attachment.binaryPropertyName as string;
const binaryData = this.helpers.assertBinaryData(index, binaryPropertyName);
let fileBase64;
if (binaryData.id) {
const chunkSize = 256 * 1024;
const stream = await this.helpers.getBinaryStream(binaryData.id, chunkSize);
const buffer = await this.helpers.binaryToBuffer(stream);
fileBase64 = buffer.toString('base64');
} else {
fileBase64 = binaryData.data;
}
data.push({
'@odata.type': '#microsoft.graph.fileAttachment',
name: binaryData.fileName,
contentBytes: fileBase64,
});
}
for (const attachment of data) {
await microsoftApiRequest.call(
this,
'POST',
`/messages/${responseData.id}/attachments`,
attachment,
{},
);
}
}
if (!saveAsDraft) {
await microsoftApiRequest.call(this, 'POST', `/messages/${responseData.id}/send`);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: index } },
);
return executionData;
}
@@ -0,0 +1,269 @@
import type {
IDataObject,
IExecuteFunctions,
INodeExecutionData,
INodeProperties,
} from 'n8n-workflow';
import { updateDisplayOptions } from '@utils/utilities';
import { createMessage } from '../../helpers/utils';
import { microsoftApiRequest } from '../../transport';
export const properties: INodeProperties[] = [
{
displayName: 'To',
name: 'toRecipients',
description: 'Comma-separated list of email addresses of recipients',
type: 'string',
required: true,
default: '',
},
{
displayName: 'Subject',
name: 'subject',
description: 'The subject of the message',
type: 'string',
default: '',
},
{
displayName: 'Message',
name: 'bodyContent',
description: 'Message body content',
type: 'string',
typeOptions: {
rows: 2,
},
default: '',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
options: [
{
displayName: 'Attachments',
name: 'attachments',
type: 'fixedCollection',
placeholder: 'Add Attachment',
default: {},
typeOptions: {
multipleValues: true,
},
options: [
{
name: 'attachments',
displayName: 'Attachment',
values: [
{
displayName: 'Input Data Field Name',
name: 'binaryPropertyName',
type: 'string',
default: '',
placeholder: 'e.g. data',
hint: 'The name of the input field containing the binary file data to be attached',
},
],
},
],
},
{
displayName: 'BCC Recipients',
name: 'bccRecipients',
description: 'Comma-separated list of email addresses of BCC recipients',
type: 'string',
default: '',
},
{
displayName: 'Category Names or IDs',
name: 'categories',
type: 'multiOptions',
description:
'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
typeOptions: {
loadOptionsMethod: 'getCategoriesNames',
},
default: [],
},
{
displayName: 'CC Recipients',
name: 'ccRecipients',
description: 'Comma-separated list of email addresses of CC recipients',
type: 'string',
default: '',
},
{
displayName: 'Custom Headers',
name: 'internetMessageHeaders',
placeholder: 'Add Header',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
options: [
{
name: 'headers',
displayName: 'Header',
values: [
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description: 'Name of the header',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'Value to set for the header',
},
],
},
],
},
{
displayName: 'From',
name: 'from',
description:
'The owner of the mailbox from which the message is sent. Must correspond to the actual mailbox used.',
type: 'string',
default: '',
},
{
displayName: 'Importance',
name: 'importance',
description: 'The importance of the message',
type: 'options',
options: [
{
name: 'Low',
value: 'Low',
},
{
name: 'Normal',
value: 'Normal',
},
{
name: 'High',
value: 'High',
},
],
default: 'Normal',
},
{
displayName: 'Message Type',
name: 'bodyContentType',
description: 'Message body content type',
type: 'options',
options: [
{
name: 'HTML',
value: 'html',
},
{
name: 'Text',
value: 'Text',
},
],
default: 'html',
},
{
displayName: 'Read Receipt Requested',
name: 'isReadReceiptRequested',
description: 'Whether a read receipt is requested for the message',
type: 'boolean',
default: false,
},
{
displayName: 'Reply To',
name: 'replyTo',
description: 'Email address to use when replying',
type: 'string',
default: '',
},
{
displayName: 'Save To Sent Items',
name: 'saveToSentItems',
description: 'Whether to save the message in Sent Items',
type: 'boolean',
default: true,
},
],
},
];
const displayOptions = {
show: {
resource: ['message'],
operation: ['send'],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(this: IExecuteFunctions, index: number, _: INodeExecutionData[]) {
const additionalFields = this.getNodeParameter('additionalFields', index);
const toRecipients = this.getNodeParameter('toRecipients', index) as string;
const subject = this.getNodeParameter('subject', index) as string;
const bodyContent = this.getNodeParameter('bodyContent', index, '') as string;
additionalFields.subject = subject;
additionalFields.bodyContent = bodyContent || ' ';
additionalFields.toRecipients = toRecipients;
const saveToSentItems =
additionalFields.saveToSentItems === undefined ? true : additionalFields.saveToSentItems;
delete additionalFields.saveToSentItems;
// Create message object from optional fields
const message: IDataObject = createMessage(additionalFields);
if (additionalFields.attachments) {
const attachments = (additionalFields.attachments as IDataObject).attachments as IDataObject[];
const messageAttachments: IDataObject[] = [];
for (const attachment of attachments) {
const binaryPropertyName = attachment.binaryPropertyName as string;
const binaryData = this.helpers.assertBinaryData(index, binaryPropertyName);
let fileBase64;
if (binaryData.id) {
const chunkSize = 256 * 1024;
const stream = await this.helpers.getBinaryStream(binaryData.id, chunkSize);
const buffer = await this.helpers.binaryToBuffer(stream);
fileBase64 = buffer.toString('base64');
} else {
fileBase64 = binaryData.data;
}
messageAttachments.push({
'@odata.type': '#microsoft.graph.fileAttachment',
name: binaryData.fileName,
contentBytes: fileBase64,
});
}
message.attachments = messageAttachments;
}
const body: IDataObject = {
message,
saveToSentItems,
};
await microsoftApiRequest.call(this, 'POST', '/sendMail', body, {});
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray({ success: true }),
{ itemData: { item: index } },
);
return executionData;
}
@@ -0,0 +1,62 @@
import type {
IDataObject,
IExecuteFunctions,
INodeExecutionData,
INodeProperties,
} from 'n8n-workflow';
import {
createEmailBodyWithN8nAttribution,
createEmailBodyWithoutN8nAttribution,
} from '../../../../../../utils/sendAndWait/email-templates';
import {
getSendAndWaitConfig,
getSendAndWaitProperties,
createButton,
} from '../../../../../../utils/sendAndWait/utils';
import { createMessage } from '../../helpers/utils';
import { microsoftApiRequest } from '../../transport';
export const description: INodeProperties[] = getSendAndWaitProperties([
{
displayName: 'To',
name: 'toRecipients',
description: 'Comma-separated list of email addresses of recipients',
type: 'string',
required: true,
default: '',
},
]);
export async function execute(this: IExecuteFunctions, index: number, items: INodeExecutionData[]) {
const toRecipients = this.getNodeParameter('toRecipients', index) as string;
const config = getSendAndWaitConfig(this);
const buttons: string[] = [];
for (const option of config.options) {
buttons.push(createButton(option.url, option.label, option.style));
}
let bodyContent: string;
if (config.appendAttribution !== false) {
const instanceId = this.getInstanceId();
bodyContent = createEmailBodyWithN8nAttribution(config.message, buttons.join('\n'), instanceId);
} else {
bodyContent = createEmailBodyWithoutN8nAttribution(config.message, buttons.join('\n'));
}
const fields: IDataObject = {
subject: config.title,
bodyContent,
toRecipients,
bodyContentType: 'html',
};
const message: IDataObject = createMessage(fields);
const body: IDataObject = { message };
await microsoftApiRequest.call(this, 'POST', '/sendMail', body);
return items;
}
@@ -0,0 +1,226 @@
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import { updateDisplayOptions } from '@utils/utilities';
import { folderRLC, messageRLC } from '../../descriptions';
import { createMessage, decodeOutlookId } from '../../helpers/utils';
import { microsoftApiRequest } from '../../transport';
export const properties: INodeProperties[] = [
messageRLC,
{
displayName: 'Update Fields',
name: 'updateFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
options: [
{
displayName: 'BCC Recipients',
name: 'bccRecipients',
description: 'Comma-separated list of email addresses of BCC recipients',
type: 'string',
default: '',
},
{
displayName: 'Category Names or IDs',
name: 'categories',
type: 'multiOptions',
description:
'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
typeOptions: {
loadOptionsMethod: 'getCategoriesNames',
},
default: [],
},
{
displayName: 'CC Recipients',
name: 'ccRecipients',
description: 'Comma-separated list of email addresses of CC recipients',
type: 'string',
default: '',
},
{
displayName: 'Custom Headers',
name: 'internetMessageHeaders',
placeholder: 'Add Header',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
options: [
{
name: 'headers',
displayName: 'Header',
values: [
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description: 'Name of the header',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'Value to set for the header',
},
],
},
],
},
{ ...folderRLC, required: false },
{
displayName: 'Importance',
name: 'importance',
description: 'The importance of the message',
type: 'options',
options: [
{
name: 'Low',
value: 'Low',
},
{
name: 'Normal',
value: 'Normal',
},
{
name: 'High',
value: 'High',
},
],
default: 'Normal',
},
{
displayName: 'Is Read',
name: 'isRead',
description: 'Whether the message must be marked as read',
type: 'boolean',
default: false,
},
{
displayName: 'Message',
name: 'bodyContent',
description: 'Message body content',
type: 'string',
typeOptions: {
rows: 2,
},
default: '',
},
{
displayName: 'Message Type',
name: 'bodyContentType',
description: 'Message body content type',
type: 'options',
options: [
{
name: 'HTML',
value: 'html',
},
{
name: 'Text',
value: 'Text',
},
],
default: 'html',
},
{
displayName: 'Read Receipt Requested',
name: 'isReadReceiptRequested',
description: 'Whether a read receipt is requested for the message',
type: 'boolean',
default: false,
},
{
displayName: 'To',
name: 'toRecipients',
description: 'Comma-separated list of email addresses of recipients',
type: 'string',
default: '',
},
{
displayName: 'Reply To',
name: 'replyTo',
description: 'Email address to use when replying',
type: 'string',
default: '',
},
{
displayName: 'Subject',
name: 'subject',
description: 'The subject of the message',
type: 'string',
default: '',
},
],
},
];
const displayOptions = {
show: {
resource: ['message'],
operation: ['update'],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(this: IExecuteFunctions, index: number) {
let responseData;
const messageId = this.getNodeParameter('messageId', index, undefined, {
extractValue: true,
}) as string;
const updateFields = this.getNodeParameter('updateFields', index);
const folderId = decodeOutlookId(
this.getNodeParameter('updateFields.folderId', index, '', {
extractValue: true,
}) as string,
);
if (folderId) {
const body: IDataObject = {
destinationId: folderId,
};
responseData = await microsoftApiRequest.call(
this,
'POST',
`/messages/${messageId}/move`,
body,
);
delete updateFields.folderId;
if (!Object.keys(updateFields).length) {
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: index } },
);
return executionData;
}
}
const body: IDataObject = createMessage(updateFields);
if (!Object.keys(body).length) {
throw new NodeOperationError(this.getNode(), 'No fields to update got specified');
}
responseData = await microsoftApiRequest.call(this, 'PATCH', `/messages/${messageId}`, body, {});
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: index } },
);
return executionData;
}