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
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,142 @@
import { ApFile, Property, createAction } from '@activepieces/pieces-framework';
import { smtpAuth } from '../..';
import { smtpCommon } from '../common';
import { Attachment, Headers } from 'nodemailer/lib/mailer';
import mime from 'mime-types';
export const sendEmail = createAction({
auth: smtpAuth,
name: 'send-email',
displayName: 'Send Email',
description: 'Send an email using a custom SMTP server.',
props: {
from: Property.ShortText({
displayName: 'From Email',
required: true,
}),
senderName: Property.ShortText({
displayName: "Sender Name",
required: false,
}),
to: Property.Array({
displayName: 'To',
required: true,
}),
cc: Property.Array({
displayName: 'CC',
required: false,
}),
replyTo: Property.ShortText({
displayName: 'Reply To',
required: false,
}),
bcc: Property.Array({
displayName: 'BCC',
required: false,
}),
subject: Property.ShortText({
displayName: 'Subject',
required: true,
}),
body_type: Property.StaticDropdown({
displayName: 'Body Type',
required: true,
defaultValue: 'plain_text',
options: {
disabled: false,
options: [
{
label: 'plain text',
value: 'plain_text',
},
{
label: 'html',
value: 'html',
},
],
},
}),
body: Property.LongText({
displayName: 'Body',
required: true,
}),
customHeaders: Property.Object({
displayName: 'Custom Headers',
required: false,
}),
attachments: Property.Array({
displayName: 'Attachments',
required: false,
properties: {
file: Property.File({
displayName: 'File',
description: 'File to attach to the email you want to send',
required: true,
}),
name: Property.ShortText({
displayName: 'Attachment Name',
description: 'In case you want to change the name of the attachment',
required: false,
}),
}
}),
},
run: async ({ auth, propsValue }) => {
const transporter = smtpCommon.createSMTPTransport(auth.props);
const attachments = propsValue['attachments'] as {file: ApFile; name: string | undefined; }[];
const attachment_data: Attachment[] = attachments.map(({file, name}) => {
const lookupResult = mime.lookup(
file.extension ? file.extension : ''
);
return {
filename: name ?? file.filename,
content: file?.base64,
contentType: lookupResult ? lookupResult : undefined,
encoding: 'base64',
};
});
const mailOptions = {
from: getFrom(propsValue.senderName, propsValue.from),
to: propsValue.to.join(','),
cc: propsValue.cc?.join(','),
inReplyTo: propsValue.replyTo,
bcc: propsValue.bcc?.join(','),
subject: propsValue.subject,
text: propsValue.body_type === 'plain_text' ? propsValue.body : undefined,
html: propsValue.body_type === 'html' ? propsValue.body : undefined,
attachments: attachment_data ? attachment_data : undefined,
headers: propsValue.customHeaders as Headers,
};
return await sendWithRetry(transporter, mailOptions);
},
});
async function sendWithRetry(transporter: any, mailOptions: any) {
const maxRetries = 3;
let retryCount = 0;
while (retryCount < maxRetries) {
try {
const info = await transporter.sendMail(mailOptions);
return info;
} catch (error: any) {
if ('code' in error && error.code === 'ECONNRESET' && retryCount < maxRetries - 1) {
retryCount++;
await new Promise(resolve => setTimeout(resolve, 3000));
continue;
}
throw error;
}
}
}
function getFrom(senderName: string|undefined, from: string) {
if (senderName) {
return `"${senderName}" <${from}>`
}
return from;
}

View File

@@ -0,0 +1,30 @@
import nodemailer from 'nodemailer';
export const smtpCommon = {
constructConfig(auth: smtpAuthParams) {
return {
host: auth.host,
port: auth.port,
requireTLS: auth.TLS,
auth: {
user: auth.email,
pass: auth.password,
},
connectionTimeout: 60000,
secure: auth.port === 465,
};
},
createSMTPTransport(auth: smtpAuthParams) {
const smtpOptions = smtpCommon.constructConfig(auth);
const transporter = nodemailer.createTransport(smtpOptions);
return transporter;
},
};
export type smtpAuthParams = {
host: string;
email: string;
password: string;
port: number;
TLS: boolean;
};