mirror of
https://github.com/n8n-io/n8n-nodes-starter.git
synced 2025-12-18 02:03:03 -06:00
Add Autosend node with Mail and Contact resources
Implement n8n node for Autosend API with support for: - Mail resource: Send single and bulk emails with template or custom content - Contact resource: Create/update contacts (upsert) and get contacts by ID or email - API key authentication - Declarative routing following n8n best practices - Full TypeScript support with proper typing - Passing all linting checks
This commit is contained in:
parent
2e9e5c61ed
commit
2f1ffde4a5
15 changed files with 1142 additions and 7 deletions
46
nodes/Autosend/resources/mail/index.ts
Normal file
46
nodes/Autosend/resources/mail/index.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
import { sendOperation } from './send';
|
||||
import { sendBulkOperation } from './sendBulk';
|
||||
|
||||
export const mailOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Send',
|
||||
value: 'send',
|
||||
description: 'Send a single email',
|
||||
action: 'Send an email',
|
||||
routing: {
|
||||
request: {
|
||||
method: 'POST',
|
||||
url: '/mails/send',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Send Bulk',
|
||||
value: 'sendBulk',
|
||||
description: 'Send emails to multiple recipients',
|
||||
action: 'Send bulk emails',
|
||||
routing: {
|
||||
request: {
|
||||
method: 'POST',
|
||||
url: '/mails/bulk',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
default: 'send',
|
||||
},
|
||||
];
|
||||
|
||||
export const mailFields: INodeProperties[] = [...sendOperation, ...sendBulkOperation];
|
||||
322
nodes/Autosend/resources/mail/send.ts
Normal file
322
nodes/Autosend/resources/mail/send.ts
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const sendOperation: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'From Email',
|
||||
name: 'fromEmail',
|
||||
type: 'string',
|
||||
required: true,
|
||||
placeholder: 'noreply@example.com',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['send'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'from.email',
|
||||
},
|
||||
},
|
||||
description: 'The email address to send from',
|
||||
},
|
||||
{
|
||||
displayName: 'From Name',
|
||||
name: 'fromName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['send'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'from.name',
|
||||
},
|
||||
},
|
||||
description: 'The name to display as the sender',
|
||||
},
|
||||
{
|
||||
displayName: 'To Email',
|
||||
name: 'toEmail',
|
||||
type: 'string',
|
||||
required: true,
|
||||
placeholder: 'recipient@example.com',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['send'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'to.email',
|
||||
},
|
||||
},
|
||||
description: 'The email address to send to',
|
||||
},
|
||||
{
|
||||
displayName: 'To Name',
|
||||
name: 'toName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['send'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'to.name',
|
||||
},
|
||||
},
|
||||
description: 'The name of the recipient',
|
||||
},
|
||||
{
|
||||
displayName: 'Email Content Type',
|
||||
name: 'contentType',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: 'template',
|
||||
options: [
|
||||
{
|
||||
name: 'Template',
|
||||
value: 'template',
|
||||
description: 'Use a pre-defined email template',
|
||||
},
|
||||
{
|
||||
name: 'Custom',
|
||||
value: 'custom',
|
||||
description: 'Provide custom HTML and text content',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['send'],
|
||||
},
|
||||
},
|
||||
description: 'Whether to use a template or custom content',
|
||||
},
|
||||
// Template-based fields
|
||||
{
|
||||
displayName: 'Template ID',
|
||||
name: 'templateId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['send'],
|
||||
contentType: ['template'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'templateId',
|
||||
},
|
||||
},
|
||||
description: 'The ID of the email template to use',
|
||||
},
|
||||
{
|
||||
displayName: 'Template Variables',
|
||||
name: 'templateVariables',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['send'],
|
||||
contentType: ['template'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'variables',
|
||||
displayName: 'Variable',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Variable name',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Variable value',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'variables',
|
||||
preSend: [
|
||||
// Transform the fixed collection into a simple object
|
||||
async function (this, requestOptions) {
|
||||
const variables = this.getNodeParameter('templateVariables') as {
|
||||
variables?: Array<{ key: string; value: string }>;
|
||||
};
|
||||
if (variables?.variables && Array.isArray(variables.variables)) {
|
||||
const variablesObj: Record<string, string> = {};
|
||||
for (const variable of variables.variables) {
|
||||
if (variable.key) {
|
||||
variablesObj[variable.key] = variable.value;
|
||||
}
|
||||
}
|
||||
requestOptions.body = requestOptions.body || {};
|
||||
(requestOptions.body as Record<string, unknown>).variables = variablesObj;
|
||||
}
|
||||
return requestOptions;
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Variables to use in the email template',
|
||||
},
|
||||
// Custom content fields
|
||||
{
|
||||
displayName: 'Subject',
|
||||
name: 'subject',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['send'],
|
||||
contentType: ['custom'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'subject',
|
||||
},
|
||||
},
|
||||
description: 'The email subject line',
|
||||
},
|
||||
{
|
||||
displayName: 'HTML Content',
|
||||
name: 'html',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
rows: 5,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['send'],
|
||||
contentType: ['custom'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'html',
|
||||
},
|
||||
},
|
||||
description: 'The HTML content of the email',
|
||||
},
|
||||
{
|
||||
displayName: 'Text Content',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
rows: 5,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['send'],
|
||||
contentType: ['custom'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'text',
|
||||
},
|
||||
},
|
||||
description: 'The plain text content of the email (fallback for non-HTML clients)',
|
||||
},
|
||||
// Additional options
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['send'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Reply To',
|
||||
name: 'replyTo',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'reply@example.com',
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'replyTo',
|
||||
},
|
||||
},
|
||||
description: 'Email address for replies',
|
||||
},
|
||||
{
|
||||
displayName: 'CC',
|
||||
name: 'cc',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'cc@example.com',
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'cc',
|
||||
},
|
||||
},
|
||||
description: 'Carbon copy recipients (comma-separated)',
|
||||
},
|
||||
{
|
||||
displayName: 'BCC',
|
||||
name: 'bcc',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'bcc@example.com',
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'bcc',
|
||||
},
|
||||
},
|
||||
description: 'Blind carbon copy recipients (comma-separated)',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
313
nodes/Autosend/resources/mail/sendBulk.ts
Normal file
313
nodes/Autosend/resources/mail/sendBulk.ts
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const sendBulkOperation: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'From Email',
|
||||
name: 'fromEmail',
|
||||
type: 'string',
|
||||
required: true,
|
||||
placeholder: 'noreply@example.com',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['sendBulk'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'from.email',
|
||||
},
|
||||
},
|
||||
description: 'The email address to send from',
|
||||
},
|
||||
{
|
||||
displayName: 'From Name',
|
||||
name: 'fromName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['sendBulk'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'from.name',
|
||||
},
|
||||
},
|
||||
description: 'The name to display as the sender',
|
||||
},
|
||||
{
|
||||
displayName: 'Recipients',
|
||||
name: 'recipients',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
multipleValueButtonText: 'Add Recipient',
|
||||
},
|
||||
required: true,
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['sendBulk'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'recipientValues',
|
||||
displayName: 'Recipient',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
placeholder: 'recipient@example.com',
|
||||
description: 'Recipient email address',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Recipient name',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'to',
|
||||
preSend: [
|
||||
async function (this, requestOptions) {
|
||||
const recipients = this.getNodeParameter('recipients') as {
|
||||
recipientValues?: Array<{ email: string; name?: string }>;
|
||||
};
|
||||
if (recipients?.recipientValues && Array.isArray(recipients.recipientValues)) {
|
||||
requestOptions.body = requestOptions.body || {};
|
||||
(requestOptions.body as Record<string, unknown>).to = recipients.recipientValues;
|
||||
}
|
||||
return requestOptions;
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'List of recipients (up to 100 per request)',
|
||||
},
|
||||
{
|
||||
displayName: 'Email Content Type',
|
||||
name: 'contentType',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: 'template',
|
||||
options: [
|
||||
{
|
||||
name: 'Template',
|
||||
value: 'template',
|
||||
description: 'Use a pre-defined email template',
|
||||
},
|
||||
{
|
||||
name: 'Custom',
|
||||
value: 'custom',
|
||||
description: 'Provide custom HTML and text content',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['sendBulk'],
|
||||
},
|
||||
},
|
||||
description: 'Whether to use a template or custom content',
|
||||
},
|
||||
// Template-based fields
|
||||
{
|
||||
displayName: 'Template ID',
|
||||
name: 'templateId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['sendBulk'],
|
||||
contentType: ['template'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'templateId',
|
||||
},
|
||||
},
|
||||
description: 'The ID of the email template to use',
|
||||
},
|
||||
{
|
||||
displayName: 'Template Variables',
|
||||
name: 'templateVariables',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['sendBulk'],
|
||||
contentType: ['template'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'variables',
|
||||
displayName: 'Variable',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Variable name',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Variable value',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'variables',
|
||||
preSend: [
|
||||
async function (this, requestOptions) {
|
||||
const variables = this.getNodeParameter('templateVariables') as {
|
||||
variables?: Array<{ key: string; value: string }>;
|
||||
};
|
||||
if (variables?.variables && Array.isArray(variables.variables)) {
|
||||
const variablesObj: Record<string, string> = {};
|
||||
for (const variable of variables.variables) {
|
||||
if (variable.key) {
|
||||
variablesObj[variable.key] = variable.value;
|
||||
}
|
||||
}
|
||||
requestOptions.body = requestOptions.body || {};
|
||||
(requestOptions.body as Record<string, unknown>).variables = variablesObj;
|
||||
}
|
||||
return requestOptions;
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Variables to use in the email template',
|
||||
},
|
||||
// Custom content fields
|
||||
{
|
||||
displayName: 'Subject',
|
||||
name: 'subject',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['sendBulk'],
|
||||
contentType: ['custom'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'subject',
|
||||
},
|
||||
},
|
||||
description: 'The email subject line',
|
||||
},
|
||||
{
|
||||
displayName: 'HTML Content',
|
||||
name: 'html',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
rows: 5,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['sendBulk'],
|
||||
contentType: ['custom'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'html',
|
||||
},
|
||||
},
|
||||
description: 'The HTML content of the email',
|
||||
},
|
||||
{
|
||||
displayName: 'Text Content',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
rows: 5,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['sendBulk'],
|
||||
contentType: ['custom'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'text',
|
||||
},
|
||||
},
|
||||
description: 'The plain text content of the email (fallback for non-HTML clients)',
|
||||
},
|
||||
// Additional options
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['mail'],
|
||||
operation: ['sendBulk'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Reply To',
|
||||
name: 'replyTo',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'reply@example.com',
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'replyTo',
|
||||
},
|
||||
},
|
||||
description: 'Email address for replies',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
Loading…
Add table
Add a link
Reference in a new issue