Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
co-authored by Claude Opus 4.5
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions
@@ -0,0 +1,43 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { Client } from '@microsoft/microsoft-graph-client';
import { microsoftOutlookAuth } from '../common/auth';
import { messageIdDropdown } from '../common/props';
export const addLabelToEmailAction = createAction({
auth: microsoftOutlookAuth,
name: 'addLabelToEmail',
displayName: 'Add Label to Email',
description: 'Adds a category (label) to an email message.',
props: {
messageId: messageIdDropdown({
displayName: 'Email',
description: 'Select the email message to add the label to.',
required: true,
}),
categories: Property.Array({
displayName: 'Categories',
description: 'Categories to add to the email.',
required: true,
}),
},
async run(context) {
const { messageId, categories } = context.propsValue;
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(context.auth.access_token),
},
});
const message = await client.api(`/me/messages/${messageId}`).get();
const existingCategories = message.categories || [];
const updatedCategories = [...new Set([...existingCategories, ...categories])];
const response = await client.api(`/me/messages/${messageId}`).patch({
categories: updatedCategories,
});
return response;
},
});
@@ -0,0 +1,108 @@
import { ApFile, createAction, Property } from '@activepieces/pieces-framework';
import { Client } from '@microsoft/microsoft-graph-client';
import { BodyType, Message } from '@microsoft/microsoft-graph-types';
import { microsoftOutlookAuth } from '../common/auth';
export const createDraftEmailAction = createAction({
auth: microsoftOutlookAuth,
name: 'createDraftEmail',
displayName: 'Create Draft Email',
description: 'Creates a draft email message.',
props: {
recipients: Property.Array({
displayName: 'To Email(s)',
required: true,
}),
ccRecipients: Property.Array({
displayName: 'CC Email(s)',
required: false,
defaultValue: [],
}),
bccRecipients: Property.Array({
displayName: 'BCC Email(s)',
required: false,
defaultValue: [],
}),
subject: Property.ShortText({
displayName: 'Subject',
required: true,
}),
bodyFormat: Property.StaticDropdown({
displayName: 'Body Format',
required: true,
defaultValue: 'text',
options: {
disabled: false,
options: [
{ label: 'HTML', value: 'html' },
{ label: 'Text', value: 'text' },
],
},
}),
body: Property.LongText({
displayName: 'Body',
required: true,
}),
attachments: Property.Array({
displayName: 'Attachments',
required: false,
defaultValue: [],
properties: {
file: Property.File({
displayName: 'File',
required: true,
}),
fileName: Property.ShortText({
displayName: 'File Name',
required: false,
}),
},
}),
},
async run(context) {
const recipients = context.propsValue.recipients as string[];
const ccRecipients = context.propsValue.ccRecipients as string[];
const bccRecipients = context.propsValue.bccRecipients as string[];
const attachments = context.propsValue.attachments as Array<{ file: ApFile; fileName: string }>;
const { subject, body, bodyFormat } = context.propsValue;
const mailPayload: Message = {
subject,
body: {
content: body,
contentType: bodyFormat as BodyType,
},
toRecipients: recipients.map((mail) => ({
emailAddress: {
address: mail,
},
})),
ccRecipients: ccRecipients.map((mail) => ({
emailAddress: {
address: mail,
},
})),
bccRecipients: bccRecipients.map((mail) => ({
emailAddress: {
address: mail,
},
})),
attachments: attachments.map((attachment) => ({
'@odata.type': '#microsoft.graph.fileAttachment',
name: attachment.fileName || attachment.file.filename,
contentBytes: attachment.file.base64,
})),
};
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(context.auth.access_token),
},
});
const response = await client.api('/me/messages').post(mailPayload);
return response;
},
});
@@ -0,0 +1,48 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { Client, PageCollection } from '@microsoft/microsoft-graph-client';
import { FileAttachment } from '@microsoft/microsoft-graph-types';
import { microsoftOutlookAuth } from '../common/auth';
export const downloadAttachmentAction = createAction({
auth: microsoftOutlookAuth,
name: 'downloadAttachment',
displayName: 'Download Attachment',
description: 'Download attachments from a specific email message.',
props: {
messageId: Property.ShortText({
displayName: 'Message ID',
description: 'The ID of the email message containing the attachment.',
required: true,
}),
},
async run(context) {
const { messageId } = context.propsValue;
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(context.auth.access_token),
},
});
const response: PageCollection = await client
.api(`/me/messages/${messageId}/attachments`)
.get();
const attachments = [];
for (const attachment of response.value as FileAttachment[]) {
if (attachment.name && attachment.contentBytes) {
attachments.push({
...attachment,
file: await context.files.write({
fileName: attachment.name || 'test.png',
data: Buffer.from(attachment.contentBytes, 'base64'),
}),
});
}
}
return attachments;
},
});
@@ -0,0 +1,73 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { Client, PageCollection } from '@microsoft/microsoft-graph-client';
import { Message } from '@microsoft/microsoft-graph-types';
import dayjs from 'dayjs';
import { microsoftOutlookAuth } from '../common/auth';
import { mailFolderIdDropdown } from '../common/props';
export const findEmailAction = createAction({
auth: microsoftOutlookAuth,
name: 'findEmail',
displayName: 'Find Email',
description: 'Searches for emails using full-text search.',
props: {
searchQuery: Property.ShortText({
displayName: 'Search Query',
description:
'Search terms to find emails (e.g., "from:john@example.com", "subject:urgent", "hasAttachments:true")',
required: true,
}),
folderId: mailFolderIdDropdown({
displayName: 'Folder',
description: 'Search in a specific folder. Leave empty to search all folders.',
required: false,
}),
top: Property.Number({
displayName: 'Max Results',
description: 'Maximum number of results to return (1-1000).',
required: false,
defaultValue: 25,
}),
},
async run(context) {
const { searchQuery, folderId, top } = context.propsValue;
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(context.auth.access_token),
},
});
const baseUrl = folderId ? `/me/mailFolders/${folderId}/messages` : '/me/messages';
const searchParam = `$search="${searchQuery}"`;
const topParam = top ? `$top=${Math.min(Math.max(top, 1), 1000)}` : '$top=25';
const selectParam = ['id', 'subject', 'from', 'toRecipients', 'receivedDateTime'].join(',');
const queryParams = [searchParam, topParam, selectParam].filter(Boolean).join('&');
const url = `${baseUrl}?${queryParams}`;
const headers: Record<string, string> = {
ConsistencyLevel: 'eventual',
Prefer: 'outlook.body-content-type="text"',
};
const response: PageCollection = await client.api(url).headers(headers).get();
const messages = response.value as Message[];
const nextPageUrl = response['@odata.nextLink'];
if (searchQuery) {
messages.sort(
(a, b) => dayjs(b.receivedDateTime).valueOf() - dayjs(a.receivedDateTime).valueOf(),
);
}
return {
found: messages.length > 0,
result: messages,
hasMore: !!nextPageUrl,
nextPageUrl: nextPageUrl,
totalCount: messages.length,
};
},
});
@@ -0,0 +1,66 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { Client } from '@microsoft/microsoft-graph-client';
import { BodyType, Message } from '@microsoft/microsoft-graph-types';
import { microsoftOutlookAuth } from '../common/auth';
import { messageIdDropdown } from '../common/props';
export const forwardEmailAction = createAction({
auth: microsoftOutlookAuth,
name: 'forwardEmail',
displayName: 'Forward Email',
description: 'Forwards an email message.',
props: {
messageId: messageIdDropdown({
displayName: 'Email',
description: 'Select the email message to forward.',
required: true,
}),
recipients: Property.Array({
displayName: 'To Email(s)',
required: true,
}),
comment: Property.LongText({
displayName: 'Comment',
description: 'Optional comment to include with the forwarded message.',
required: false,
}),
},
async run(context) {
const { messageId, comment } = context.propsValue;
const recipients = context.propsValue.recipients as string[];
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(context.auth.access_token),
},
});
const message = await client.api(`/me/messages/${messageId}`).get();
const messagePayload: Message = {
toRecipients: recipients.map((mail) => ({
emailAddress: {
address: mail,
},
})),
body: {
contentType: 'html',
content: (comment ?? '') + '<br><br>' + message.body.content,
},
attachments: message.attachments,
};
const response = await client
.api(`/me/messages/${messageId}/forward`)
.post({
message:messagePayload,
});
return {
success: true,
message: 'Email forwarded successfully.',
messageId: response.id,
...response,
};
},
});
@@ -0,0 +1,39 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { Client, PageCollection } from '@microsoft/microsoft-graph-client';
import { MailFolder } from '@microsoft/microsoft-graph-types';
import { microsoftOutlookAuth } from '../common/auth';
import { mailFolderIdDropdown, messageIdDropdown } from '../common/props';
export const moveEmailToFolderAction = createAction({
auth: microsoftOutlookAuth,
name: 'moveEmailToFolder',
displayName: 'Move Email to Folder',
description: 'Moves an email message to a specific folder.',
props: {
messageId: messageIdDropdown({
displayName: 'Email',
description: 'Select the email message to move.',
required: true,
}),
destinationFolderId: mailFolderIdDropdown({
displayName: 'Destination Folder',
description: 'The folder to move the email to.',
required: true,
}),
},
async run(context) {
const { messageId, destinationFolderId } = context.propsValue;
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(context.auth.access_token),
},
});
const response = await client.api(`/me/messages/${messageId}/move`).post({
destinationId: destinationFolderId,
});
return response;
},
});
@@ -0,0 +1,45 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { Client } from '@microsoft/microsoft-graph-client';
import { microsoftOutlookAuth } from '../common/auth';
import { messageIdDropdown } from '../common/props';
export const removeLabelFromEmailAction = createAction({
auth: microsoftOutlookAuth,
name: 'removeLabelFromEmail',
displayName: 'Remove Label from Email',
description: 'Removes a category (label) from an email message.',
props: {
messageId: messageIdDropdown({
displayName: 'Email',
description: 'Select the email message to remove the label from.',
required: true,
}),
categories: Property.Array({
displayName: 'Categories to Remove',
description: 'Categories to remove from the email.',
required: true,
}),
},
async run(context) {
const { messageId, categories } = context.propsValue;
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(context.auth.access_token),
},
});
const message = await client.api(`/me/messages/${messageId}`).get();
const existingCategories = message.categories || [];
const updatedCategories = existingCategories.filter(
(category: string) => !categories.includes(category)
);
const response = await client.api(`/me/messages/${messageId}`).patch({
categories: updatedCategories,
});
return response;
},
});
@@ -0,0 +1,163 @@
import { ApFile, createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { microsoftOutlookAuth } from '../common/auth';
import { BodyType, Message } from '@microsoft/microsoft-graph-types';
import { Client, PageCollection } from '@microsoft/microsoft-graph-client';
export const replyEmailAction = createAction({
auth: microsoftOutlookAuth,
name: 'reply-email',
displayName: 'Reply to Email',
description: 'Reply to an outlook email.',
props: {
messageId: Property.Dropdown({
auth: microsoftOutlookAuth,
displayName: 'Email',
description: 'Select the email message to reply to.',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
};
}
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve((auth as OAuth2PropertyValue).access_token),
},
});
try {
const response: PageCollection = await client
.api('/me/messages?$top=50&$select=id,subject,from,receivedDateTime')
.orderby('receivedDateTime desc')
.get();
const messages = response.value as Message[];
return {
disabled: false,
options: messages.map((message) => ({
label: `${message.subject || 'No Subject'} - ${message.from?.emailAddress?.name || message.from?.emailAddress?.address || 'Unknown Sender'}`,
value: message.id || '',
})),
};
} catch (error) {
return {
disabled: true,
options: [],
};
}
},
}),
bodyFormat: Property.StaticDropdown({
displayName: 'Body Format',
required: true,
defaultValue: 'text',
options: {
disabled: false,
options: [
{ label: 'HTML', value: 'html' },
{ label: 'Text', value: 'text' },
],
},
}),
replyBody: Property.LongText({
displayName: 'Reply Body',
required: true,
}),
ccRecipients: Property.Array({
displayName: 'CC Recipients',
required: false,
}),
bccRecipients: Property.Array({
displayName: 'BCC Recipients',
required: false,
}),
attachments: Property.Array({
displayName: 'Attachments',
required: false,
defaultValue: [],
properties: {
file: Property.File({
displayName: 'File',
required: true,
}),
fileName: Property.ShortText({
displayName: 'File Name',
required: false,
}),
},
}),
draft: Property.Checkbox({
displayName: 'Create Draft',
description: 'If enabled, creates draft without sending.',
required: true,
defaultValue: false,
}),
},
async run(context) {
const { replyBody, bodyFormat, messageId, draft } = context.propsValue;
const ccRecipients = context.propsValue.ccRecipients as string[];
const bccRecipients = context.propsValue.bccRecipients as string[];
const attachments = context.propsValue.attachments as Array<{
file: ApFile;
fileName: string;
}>;
const mailPayload: Message = {
body: {
content: replyBody,
contentType: bodyFormat as BodyType,
},
ccRecipients: ccRecipients.map((mail) => ({
emailAddress: {
address: mail,
},
})),
bccRecipients: bccRecipients.map((mail) => ({
emailAddress: {
address: mail,
},
})),
attachments: attachments.map((attachment) => ({
'@odata.type': '#microsoft.graph.fileAttachment',
name: attachment.fileName || attachment.file.filename,
contentBytes: attachment.file.base64,
})),
};
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(context.auth.access_token),
},
});
try {
const response: Message = await client
.api(`/me/messages/${messageId}/createReply`)
.post({
message: mailPayload,
});
const draftId = response.id;
if (!draft) {
await client.api(`/me/messages/${draftId}/send`).post({});
return {
success: true,
message: 'Reply sent successfully.',
draftId: draftId,
};
}
return {
success: true,
message: 'Draft created successfully.',
draftId: draftId,
draftLink: `https://outlook.office.com/mail/drafts/id/${draftId}`,
};
} catch (error) {
console.error('Reply Email Error:', error);
const errorMessage =
error instanceof Error ? error.message : 'Unknown error';
throw new Error(errorMessage);
}
},
});
@@ -0,0 +1,35 @@
import { createAction } from '@activepieces/pieces-framework';
import { Client } from '@microsoft/microsoft-graph-client';
import { microsoftOutlookAuth } from '../common/auth';
import { draftMessageIdDropdown } from '../common/props';
export const sendDraftEmailAction = createAction({
auth: microsoftOutlookAuth,
name: 'sendDraftEmail',
displayName: 'Send Draft Email',
description: 'Sends a draft email message.',
props: {
messageId: draftMessageIdDropdown({
displayName: 'Draft Email',
description: 'Select the draft email message to send.',
required: true,
}),
},
async run(context) {
const { messageId } = context.propsValue;
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(context.auth.access_token),
},
});
await client.api(`/me/messages/${messageId}/send`).post({});
return {
success: true,
message: 'Draft sent successfully.',
messageId: messageId,
};
},
});
@@ -0,0 +1,112 @@
import { ApFile, createAction, Property } from '@activepieces/pieces-framework';
import { Client } from '@microsoft/microsoft-graph-client';
import { BodyType, Message } from '@microsoft/microsoft-graph-types';
import { microsoftOutlookAuth } from '../common/auth';
export const sendEmailAction = createAction({
auth: microsoftOutlookAuth,
name: 'send-email',
displayName: 'Send Email',
description: 'Sends an email using Microsoft Outlook.',
props: {
recipients: Property.Array({
displayName: 'To Email(s)',
required: true,
}),
ccRecipients: Property.Array({
displayName: 'CC Email(s)',
required: false,
defaultValue: [],
}),
bccRecipients: Property.Array({
displayName: 'BCC Email(s)',
required: false,
defaultValue: [],
}),
subject: Property.ShortText({
displayName: 'Subject',
required: true,
}),
bodyFormat: Property.StaticDropdown({
displayName: 'Body Format',
required: true,
defaultValue: 'text',
options: {
disabled: false,
options: [
{ label: 'HTML', value: 'html' },
{ label: 'Text', value: 'text' },
],
},
}),
body: Property.LongText({
displayName: 'Body',
required: true,
}),
attachments: Property.Array({
displayName: 'Attachments',
required: false,
defaultValue: [],
properties: {
file: Property.File({
displayName: 'File',
required: true,
}),
fileName: Property.ShortText({
displayName: 'File Name',
required: false,
}),
},
}),
},
async run(context) {
const recipients = context.propsValue.recipients as string[];
const ccRecipients = context.propsValue.ccRecipients as string[];
const bccRecipients = context.propsValue.bccRecipients as string[];
const attachments = context.propsValue.attachments as Array<{ file: ApFile; fileName: string }>;
const { subject, body, bodyFormat } = context.propsValue;
const mailPayload: Message = {
subject,
body: {
content: body,
contentType: bodyFormat as BodyType,
},
toRecipients: recipients.map((mail) => ({
emailAddress: {
address: mail,
},
})),
ccRecipients: ccRecipients.map((mail) => ({
emailAddress: {
address: mail,
},
})),
bccRecipients: bccRecipients.map((mail) => ({
emailAddress: {
address: mail,
},
})),
attachments: attachments.map((attachment) => ({
'@odata.type': '#microsoft.graph.fileAttachment',
name: attachment.fileName || attachment.file.filename,
contentBytes: attachment.file.base64,
})),
};
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(context.auth.access_token),
},
});
const response = await client.api('/me/sendMail').post({
message: mailPayload,
saveToSentItems: 'true',
});
return response;
},
});
@@ -0,0 +1,54 @@
import { OAuth2PropertyValue, PieceAuth } from '@activepieces/pieces-framework';
import { Client } from '@microsoft/microsoft-graph-client';
const authDesc = `
1. Sign in to [Microsoft Azure Portal](https://portal.azure.com/).
2. From the left sidebar, go to **Microsoft Enfra ID**.
3. Under **Manage**, click on **App registrations**.
4. Click the **New registration** button.
5. Enter a **Name** for your app.
6. For **Supported account types**, choose:
- **Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant) and personal Microsoft accounts**
- Or select based on your requirement.
7. In **Redirect URI**, select **Web** and add the given URL.
8. Click **Register**.
9. After registration, youll be redirected to the apps overview page. Copy the **Application (client) ID**.
10. From the left menu, go to **Certificates & secrets**.
- Under **Client secrets**, click **New client secret**.
- Provide a description, set an expiry, and click **Add**.
- Copy the **Value** of the client secret (this will not be shown again).
11. Go to **API permissions** from the left menu.
- Click **Add a permission**.
- Select **Microsoft Graph** → **Delegated permissions**.
- Add the following scopes:
- Mail.ReadWrite
- Mail.Send
- Calendars.Read
- offline_access
- User.Read
- Click **Add permissions**.
12. Copy your **Client ID** and **Client Secret**.
`
export const microsoftOutlookAuth = PieceAuth.OAuth2({
description:authDesc,
authUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
required: true,
scope: ['Mail.ReadWrite', 'Mail.Send', 'Calendars.Read', 'offline_access', 'User.Read'],
prompt: 'omit',
validate: async ({ auth }) => {
try {
const authValue = auth as OAuth2PropertyValue;
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(authValue.access_token),
},
});
await client.api('/me').get();
return { valid: true };
} catch (error) {
return { valid: false, error: 'Invalid Credentials.' };
}
},
});
@@ -0,0 +1,145 @@
import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework';
import { PageCollection, Client } from '@microsoft/microsoft-graph-client';
import { MailFolder, Message } from '@microsoft/microsoft-graph-types';
import { microsoftOutlookAuth } from './auth';
type DropdownParams = {
displayName: string;
description: string;
required: boolean;
};
export const messageIdDropdown = (params: DropdownParams) =>
Property.Dropdown({
auth: microsoftOutlookAuth,
displayName: params.displayName,
description: params.description,
required: params.required,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
placeholder: 'Please connect your account first.',
disabled: true,
options: [],
};
}
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve((auth as OAuth2PropertyValue).access_token),
},
});
try {
const response: PageCollection = await client
.api('/me/messages?$top=50&$select=id,subject,from,receivedDateTime')
.orderby('receivedDateTime desc')
.get();
const messages = response.value as Message[];
return {
disabled: false,
options: messages.map((message) => ({
label: `${message.subject || 'No Subject'}`,
value: message.id,
})),
};
} catch (error) {
return {
disabled: true,
options: [],
};
}
},
});
export const draftMessageIdDropdown = (params: DropdownParams) =>
Property.Dropdown({
auth: microsoftOutlookAuth,
displayName: params.displayName,
description: params.description,
required: params.required,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
placeholder: 'Please connect your account first.',
disabled: true,
options: [],
};
}
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve((auth as OAuth2PropertyValue).access_token),
},
});
try {
const response: PageCollection = await client
.api('/me/mailFolders/drafts/messages?$top=50&$select=id,subject,from,receivedDateTime')
.orderby('receivedDateTime desc')
.get();
const messages = response.value as Message[];
return {
disabled: false,
options: messages.map((message) => ({
label: `${message.subject || 'No Subject'}`,
value: message.id,
})),
};
} catch (error) {
return {
disabled: true,
options: [],
};
}
},
});
export const mailFolderIdDropdown = (params: DropdownParams) =>
Property.Dropdown({
auth: microsoftOutlookAuth,
displayName: params.displayName,
description: params.description,
required: params.required,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
placeholder: 'Please connect your account first.',
disabled: true,
options: [],
};
}
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve((auth as OAuth2PropertyValue).access_token),
},
});
try {
const response: PageCollection = await client.api('/me/mailFolders').get();
const folders = response.value as MailFolder[];
return {
disabled: false,
options: folders.map((folder) => ({
label: folder.displayName || folder.id || 'Unknown',
value: folder.id || '',
})),
};
} catch (error) {
return {
disabled: true,
options: [],
};
}
},
});
@@ -0,0 +1,136 @@
import { FilesService, TriggerStrategy, createTrigger } from '@activepieces/pieces-framework';
import { Client, PageCollection } from '@microsoft/microsoft-graph-client';
import { Message, FileAttachment } from '@microsoft/microsoft-graph-types';
import dayjs from 'dayjs';
import { microsoftOutlookAuth } from '../common/auth';
import { mailFolderIdDropdown } from '../common/props';
import { isNil } from '@activepieces/shared';
async function enrichAttachments(
client: Client,
messages: Message[],
files: FilesService,
): Promise<Record<string, any>[]> {
const attachments: Record<string, any>[] = [];
for (const message of messages) {
const attachmentResponse: PageCollection = await client
.api(`/me/messages/${message.id}/attachments`)
.get();
for (const attachment of attachmentResponse.value as FileAttachment[]) {
const { contentBytes, ...rest } = attachment;
if (attachment.name && contentBytes) {
const file = await files.write({
fileName: attachment.name,
data: Buffer.from(contentBytes, 'base64'),
});
attachments.push({
file,
messageId: message.id!,
messageSubject: message.subject,
messageSender: message.sender,
messageReceivedDateTime: message.receivedDateTime,
parentFolderId: message.parentFolderId,
...rest,
});
}
}
}
return attachments;
}
export const newAttachmentTrigger = createTrigger({
auth: microsoftOutlookAuth,
name: 'newAttachment',
displayName: 'New Attachment',
description: 'Triggers when a new email containing one or more attachments arrives.',
props: {
folderId: mailFolderIdDropdown({
displayName: 'Folder',
description: 'Monitor attachments in a specific folder. Leave empty to monitor all folders.',
required: false,
}),
},
sampleData: {},
type: TriggerStrategy.POLLING,
async onEnable(context) {
await context.store.put('lastPoll', Date.now());
},
async onDisable(context) {
// return
},
async test(context) {
const { folderId } = context.propsValue;
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(context.auth.access_token),
},
});
const baseUrl = folderId ? `/me/mailFolders/${folderId}/messages` : '/me/messages';
const response: PageCollection = await client
.api(`${baseUrl}?$filter=hasAttachments eq true`)
.top(10)
.get();
const attachments = await enrichAttachments(client, response.value as Message[], context.files);
const items = attachments.map((attachment) => ({
epochMilliSeconds: dayjs(attachment['messageReceivedDateTime']).valueOf(),
data: attachment,
}));
return items.map((item) => item.data);
},
async run(context) {
const lastFetchEpochMS = await context.store.get<number>('lastPoll');
if (isNil(lastFetchEpochMS)) {
throw new Error("lastPoll doesn't exist in the store.");
}
const { folderId } = context.propsValue;
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(context.auth.access_token),
},
});
const baseUrl = folderId ? `/me/mailFolders/${folderId}/messages` : '/me/messages';
let response: PageCollection = await client
.api(
`${baseUrl}?$filter=receivedDateTime gt ${dayjs(
lastFetchEpochMS,
).toISOString()} and hasAttachments eq true`,
)
.orderby('receivedDateTime desc')
.get();
const messages: Message[] = [];
while (response.value.length > 0) {
messages.push(...(response.value as Message[]));
if (response['@odata.nextLink']) {
response = await client.api(response['@odata.nextLink']).get();
} else {
break;
}
}
const attachments = await enrichAttachments(client, messages, context.files);
const items = attachments.map((attachment) => ({
epochMilliSeconds: dayjs(attachment['messageReceivedDateTime']).valueOf(),
data: attachment,
}));
const newLastEpochMilliSeconds = items.reduce(
(acc, item) => Math.max(acc, item.epochMilliSeconds),
lastFetchEpochMS,
);
await context.store.put('lastPoll', newLastEpochMilliSeconds);
return items.filter((f) => f.epochMilliSeconds > lastFetchEpochMS).map((item) => item.data);
},
});
@@ -0,0 +1,95 @@
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import {
AppConnectionValueForAuthProperty,
PiecePropValueSchema,
TriggerStrategy,
createTrigger,
} from '@activepieces/pieces-framework';
import { Client, PageCollection } from '@microsoft/microsoft-graph-client';
import { Message } from '@microsoft/microsoft-graph-types';
import dayjs from 'dayjs';
import { microsoftOutlookAuth } from '../common/auth';
import { mailFolderIdDropdown } from '../common/props';
const polling: Polling<AppConnectionValueForAuthProperty<typeof microsoftOutlookAuth>, { folderId?: string }> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, lastFetchEpochMS, propsValue }) => {
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(auth.access_token),
},
});
const messages = [];
const folderId = propsValue.folderId;
const filter =
lastFetchEpochMS === 0
? '$top=10'
: `$filter=createdDateTime gt ${dayjs(lastFetchEpochMS).toISOString()}`;
let response: PageCollection = await client
.api(`/me/mailFolders/${folderId}/messages?${filter}`)
.orderby('createdDateTime desc')
.get();
if (lastFetchEpochMS === 0) {
for (const message of response.value as Message[]) {
messages.push(message);
}
} else {
while (response.value.length > 0) {
for (const message of response.value as Message[]) {
messages.push(message);
}
if (response['@odata.nextLink']) {
response = await client.api(response['@odata.nextLink']).get();
} else {
break;
}
}
}
return messages.map((message) => ({
epochMilliSeconds: dayjs(message.createdDateTime).valueOf(),
data: message,
}));
},
};
export const newEmailInFolderTrigger = createTrigger({
auth: microsoftOutlookAuth,
name: 'newEmailInFolder',
displayName: 'New Email in Folder',
description: 'Triggers when a new email is delivered into the specified folder.',
props: {
folderId: mailFolderIdDropdown({
displayName: 'Folder',
description: '',
required: true,
}),
},
sampleData: {},
type: TriggerStrategy.POLLING,
async onEnable(context) {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async onDisable(context) {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async test(context) {
return await pollingHelper.test(polling, context);
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
});
@@ -0,0 +1,87 @@
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import {
AppConnectionValueForAuthProperty,
PiecePropValueSchema,
TriggerStrategy,
createTrigger,
} from '@activepieces/pieces-framework';
import { Client, PageCollection } from '@microsoft/microsoft-graph-client';
import { Message } from '@microsoft/microsoft-graph-types';
import dayjs from 'dayjs';
import { microsoftOutlookAuth } from '../common/auth';
const polling: Polling<AppConnectionValueForAuthProperty<typeof microsoftOutlookAuth>, Record<string,any>> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, lastFetchEpochMS }) => {
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(auth.access_token),
},
});
const messages = [];
const filter =
lastFetchEpochMS === 0
? '$top=10'
: `$filter=receivedDateTime gt ${dayjs(lastFetchEpochMS).toISOString()}`;
let response: PageCollection = await client
.api(`/me/mailFolders/inbox/messages?${filter}`)
.orderby('receivedDateTime desc')
.get();
if (lastFetchEpochMS === 0) {
for (const message of response.value as Message[]) {
messages.push(message);
}
} else {
while (response.value.length > 0) {
for (const message of response.value as Message[]) {
messages.push(message);
}
if (response['@odata.nextLink']) {
response = await client.api(response['@odata.nextLink']).get();
} else {
break;
}
}
}
return messages.map((message) => ({
epochMilliSeconds: dayjs(message.receivedDateTime).valueOf(),
data: message,
}));
},
};
export const newEmailTrigger = createTrigger({
auth: microsoftOutlookAuth,
name: 'newEmail',
displayName: 'New Email',
description: 'Triggers when a new email is received in the inbox.',
props: {},
sampleData: {},
type: TriggerStrategy.POLLING,
async onEnable(context) {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async onDisable(context) {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async test(context) {
return await pollingHelper.test(polling, context);
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
});