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:
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user