mirror of
https://github.com/n8n-io/n8n-nodes-starter.git
synced 2025-12-15 00:53:02 -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
53
credentials/AutosendApi.credentials.ts
Normal file
53
credentials/AutosendApi.credentials.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import type {
|
||||
IAuthenticateGeneric,
|
||||
ICredentialTestRequest,
|
||||
ICredentialType,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class AutosendApi implements ICredentialType {
|
||||
name = 'autosendApi';
|
||||
|
||||
displayName = 'Autosend API';
|
||||
|
||||
documentationUrl = 'https://docs.autosend.com/';
|
||||
|
||||
icon = 'file:../icons/autosend.svg' as const;
|
||||
|
||||
httpRequestNode = {
|
||||
name: 'Autosend',
|
||||
docsUrl: 'https://docs.autosend.com/',
|
||||
apiBaseUrl: 'https://api.autosend.com/',
|
||||
};
|
||||
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
password: true,
|
||||
},
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'API key from your Autosend account settings',
|
||||
},
|
||||
];
|
||||
|
||||
authenticate: IAuthenticateGeneric = {
|
||||
type: 'generic',
|
||||
properties: {
|
||||
headers: {
|
||||
Authorization: '=Bearer {{$credentials.apiKey}}',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
test: ICredentialTestRequest = {
|
||||
request: {
|
||||
baseURL: 'https://api.autosend.com',
|
||||
url: '/contacts',
|
||||
method: 'GET',
|
||||
},
|
||||
};
|
||||
}
|
||||
6
icons/autosend.dark.svg
Normal file
6
icons/autosend.dark.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="4" width="20" height="16" rx="2"/>
|
||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>
|
||||
<path d="m2 13 4 3"/>
|
||||
<path d="m22 13-4 3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 318 B |
6
icons/autosend.svg
Normal file
6
icons/autosend.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="4" width="20" height="16" rx="2"/>
|
||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>
|
||||
<path d="m2 13 4 3"/>
|
||||
<path d="m22 13-4 3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 323 B |
18
nodes/Autosend/Autosend.node.json
Normal file
18
nodes/Autosend/Autosend.node.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"node": "n8n-nodes-base.autosend",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Communication"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.autosend.com/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.autosend.com/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
71
nodes/Autosend/Autosend.node.ts
Normal file
71
nodes/Autosend/Autosend.node.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import type {
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
INodeExecutionData,
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-workflow';
|
||||
import { mailFields, mailOperations } from './resources/mail';
|
||||
import { contactFields, contactOperations } from './resources/contact';
|
||||
|
||||
export class Autosend implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Autosend',
|
||||
name: 'autosend',
|
||||
icon: 'file:autosend.svg',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Interact with Autosend API to send emails and manage contacts',
|
||||
defaults: {
|
||||
name: 'Autosend',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'autosendApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
requestDefaults: {
|
||||
baseURL: 'https://api.autosend.com',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Mail',
|
||||
value: 'mail',
|
||||
description: 'Send emails using Autosend',
|
||||
},
|
||||
{
|
||||
name: 'Contact',
|
||||
value: 'contact',
|
||||
description: 'Manage contacts in Autosend',
|
||||
},
|
||||
],
|
||||
default: 'mail',
|
||||
},
|
||||
...mailOperations,
|
||||
...mailFields,
|
||||
...contactOperations,
|
||||
...contactFields,
|
||||
],
|
||||
usableAsTool: true,
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
// This method is required but the declarative routing handles everything
|
||||
// This is only called if routing doesn't handle the request
|
||||
const items = this.getInputData();
|
||||
return [items];
|
||||
}
|
||||
}
|
||||
6
nodes/Autosend/autosend.dark.svg
Normal file
6
nodes/Autosend/autosend.dark.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="4" width="20" height="16" rx="2"/>
|
||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>
|
||||
<path d="m2 13 4 3"/>
|
||||
<path d="m22 13-4 3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 318 B |
6
nodes/Autosend/autosend.svg
Normal file
6
nodes/Autosend/autosend.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="4" width="20" height="16" rx="2"/>
|
||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>
|
||||
<path d="m2 13 4 3"/>
|
||||
<path d="m22 13-4 3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 323 B |
80
nodes/Autosend/resources/contact/get.ts
Normal file
80
nodes/Autosend/resources/contact/get.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const getOperation: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Search By',
|
||||
name: 'searchBy',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: 'email',
|
||||
options: [
|
||||
{
|
||||
name: 'Email',
|
||||
value: 'email',
|
||||
description: 'Search contact by email address',
|
||||
},
|
||||
{
|
||||
name: 'ID',
|
||||
value: 'id',
|
||||
description: 'Search contact by ID',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['contact'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
description: 'How to search for the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['contact'],
|
||||
operation: ['get'],
|
||||
searchBy: ['id'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
request: {
|
||||
url: '=/contacts/{{$parameter.contactId}}',
|
||||
},
|
||||
},
|
||||
description: 'The ID of the contact to retrieve',
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
required: true,
|
||||
placeholder: 'contact@example.com',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['contact'],
|
||||
operation: ['get'],
|
||||
searchBy: ['email'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'emails',
|
||||
preSend: [
|
||||
async function (this, requestOptions) {
|
||||
const email = this.getNodeParameter('email') as string;
|
||||
requestOptions.body = requestOptions.body || {};
|
||||
(requestOptions.body as Record<string, unknown>).emails = [email];
|
||||
return requestOptions;
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The email address of the contact to search for',
|
||||
},
|
||||
];
|
||||
64
nodes/Autosend/resources/contact/index.ts
Normal file
64
nodes/Autosend/resources/contact/index.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
import { upsertOperation } from './upsert';
|
||||
import { getOperation } from './get';
|
||||
|
||||
export const contactOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['contact'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create or Update',
|
||||
value: 'upsert',
|
||||
description: 'Create a new record, or update the current one if it already exists (upsert)',
|
||||
action: 'Create or update a contact',
|
||||
routing: {
|
||||
request: {
|
||||
method: 'POST' as const,
|
||||
url: '/contacts/email',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a contact by ID or email',
|
||||
action: 'Get a contact',
|
||||
routing: {
|
||||
request: {
|
||||
method: 'GET' as const,
|
||||
url: '/contacts/{{$parameter.contactId}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
default: 'upsert',
|
||||
},
|
||||
];
|
||||
|
||||
export const contactFields: INodeProperties[] = [
|
||||
...upsertOperation,
|
||||
...getOperation.map((field) => {
|
||||
// Override routing for email search to use the search endpoint
|
||||
if (field.name === 'email' && field.routing) {
|
||||
return {
|
||||
...field,
|
||||
routing: {
|
||||
...field.routing,
|
||||
request: {
|
||||
method: 'POST' as const,
|
||||
url: '/contacts/search/emails',
|
||||
},
|
||||
},
|
||||
} as INodeProperties;
|
||||
}
|
||||
return field;
|
||||
}),
|
||||
];
|
||||
142
nodes/Autosend/resources/contact/upsert.ts
Normal file
142
nodes/Autosend/resources/contact/upsert.ts
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const upsertOperation: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
required: true,
|
||||
placeholder: 'contact@example.com',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['contact'],
|
||||
operation: ['upsert'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'email',
|
||||
},
|
||||
},
|
||||
description: 'The email address of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'User ID',
|
||||
name: 'userId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['contact'],
|
||||
operation: ['upsert'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'userId',
|
||||
},
|
||||
},
|
||||
description: 'External user ID from your system',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['contact'],
|
||||
operation: ['upsert'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Company',
|
||||
name: 'company',
|
||||
type: 'string',
|
||||
default: '',
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'company',
|
||||
},
|
||||
},
|
||||
description: 'Company name',
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Fields (JSON)',
|
||||
name: 'customFields',
|
||||
type: 'json',
|
||||
default: '{}',
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'customFields',
|
||||
preSend: [
|
||||
async function (this, requestOptions) {
|
||||
const customFieldsStr = this.getNodeParameter(
|
||||
'additionalFields.customFields',
|
||||
'',
|
||||
) as string;
|
||||
if (customFieldsStr) {
|
||||
try {
|
||||
const customFields = JSON.parse(customFieldsStr);
|
||||
requestOptions.body = requestOptions.body || {};
|
||||
(requestOptions.body as Record<string, unknown>).customFields = customFields;
|
||||
} catch {
|
||||
throw new Error('Custom Fields must be valid JSON');
|
||||
}
|
||||
}
|
||||
return requestOptions;
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Additional custom fields as a JSON object',
|
||||
},
|
||||
{
|
||||
displayName: 'First Name',
|
||||
name: 'firstName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'firstName',
|
||||
},
|
||||
},
|
||||
description: 'First name of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Last Name',
|
||||
name: 'lastName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'lastName',
|
||||
},
|
||||
},
|
||||
description: 'Last name of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Phone',
|
||||
name: 'phone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'phone',
|
||||
},
|
||||
},
|
||||
description: 'Phone number of the contact',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
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',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "n8n-nodes-<...>",
|
||||
"name": "n8n-nodes-autosend",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "n8n-nodes-<...>",
|
||||
"name": "n8n-nodes-autosend",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
|
|
|
|||
12
package.json
12
package.json
|
|
@ -1,19 +1,19 @@
|
|||
{
|
||||
"name": "n8n-nodes-<...>",
|
||||
"name": "n8n-nodes-autosend",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"description": "n8n node for Autosend - Send emails and manage contacts",
|
||||
"license": "MIT",
|
||||
"homepage": "",
|
||||
"keywords": [
|
||||
"n8n-community-node-package"
|
||||
],
|
||||
"author": {
|
||||
"name": "",
|
||||
"email": ""
|
||||
"name": "Your Name",
|
||||
"email": "your.email@example.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/<...>/n8n-nodes-<...>.git"
|
||||
"url": "https://github.com/codebuster22/autosend-n8n-nodes.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "n8n-node build",
|
||||
|
|
@ -31,10 +31,12 @@
|
|||
"n8nNodesApiVersion": 1,
|
||||
"strict": true,
|
||||
"credentials": [
|
||||
"dist/credentials/AutosendApi.credentials.js",
|
||||
"dist/credentials/GithubIssuesApi.credentials.js",
|
||||
"dist/credentials/GithubIssuesOAuth2Api.credentials.js"
|
||||
],
|
||||
"nodes": [
|
||||
"dist/nodes/Autosend/Autosend.node.js",
|
||||
"dist/nodes/GithubIssues/GithubIssues.node.js",
|
||||
"dist/nodes/Example/Example.node.js"
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue