init: n8n community node for sending emails rendered from Markdown via emailmd
This commit is contained in:
13
.eslintrc.js
Normal file
13
.eslintrc.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
es2020: true,
|
||||||
|
},
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['eslint-plugin-n8n-nodes-base'],
|
||||||
|
extends: ['plugin:eslint-plugin-n8n-nodes-base/nodes'],
|
||||||
|
rules: {
|
||||||
|
'n8n-nodes-base/node-param-description-missing-from-dynamic-options': 'off',
|
||||||
|
},
|
||||||
|
};
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
*.js.map
|
||||||
|
.env
|
||||||
8
.prettierrc.js
Normal file
8
.prettierrc.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
semi: true,
|
||||||
|
singleQuote: true,
|
||||||
|
trailingComma: 'all',
|
||||||
|
printWidth: 100,
|
||||||
|
tabWidth: 2,
|
||||||
|
useTabs: true,
|
||||||
|
};
|
||||||
102
README.md
Normal file
102
README.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# n8n-nodes-emailmd
|
||||||
|
|
||||||
|
n8n community node do wysyłania emaili renderowanych z Markdown za pomocą [emailmd](https://github.com/unmta/emailmd).
|
||||||
|
|
||||||
|
## Funkcje
|
||||||
|
|
||||||
|
- Pisz treść emaila w Markdown zamiast HTML
|
||||||
|
- Obsługa YAML frontmatter (`preheader`, `theme`)
|
||||||
|
- Wbudowane motywy: `default`, `light`, `dark`
|
||||||
|
- Nadpisywanie kolorów i typografii motywu
|
||||||
|
- Wsparcie dla: To, CC, BCC, Reply-To, attachments
|
||||||
|
- Automatyczna generacja wersji plain-text
|
||||||
|
- Dyrektywy emailmd: `{button}`, `:::callout`, `:::hero`, `:::footer`
|
||||||
|
|
||||||
|
## Instalacja
|
||||||
|
|
||||||
|
### Opcja 1 — npm link (lokalny development)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Zbuduj node
|
||||||
|
cd /path/to/n8n-nodes-emailmd
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 2. Zlinkuj lokalnie
|
||||||
|
npm link
|
||||||
|
|
||||||
|
# 3. W katalogu n8n
|
||||||
|
cd ~/.n8n
|
||||||
|
mkdir -p custom
|
||||||
|
cd custom
|
||||||
|
npm link n8n-nodes-emailmd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Opcja 2 — Ścieżka do custom nodes w n8n
|
||||||
|
|
||||||
|
Ustaw zmienną środowiskową przed uruchomieniem n8n:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export N8N_CUSTOM_EXTENSIONS="/path/to/n8n-nodes-emailmd"
|
||||||
|
n8n start
|
||||||
|
```
|
||||||
|
|
||||||
|
### Opcja 3 — npm install (po opublikowaniu)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install n8n-nodes-emailmd
|
||||||
|
```
|
||||||
|
|
||||||
|
## Konfiguracja
|
||||||
|
|
||||||
|
### Credentials (SMTP)
|
||||||
|
|
||||||
|
Dodaj credentials typu **SMTP**:
|
||||||
|
- **Host** — serwer SMTP (np. `smtp.gmail.com`)
|
||||||
|
- **Port** — `465` (SSL) lub `587` (STARTTLS)
|
||||||
|
- **Secure** — `true` dla portu 465
|
||||||
|
- **User** / **Password**
|
||||||
|
|
||||||
|
### Parametry node
|
||||||
|
|
||||||
|
| Pole | Opis |
|
||||||
|
|---|---|
|
||||||
|
| From Name | Nazwa nadawcy |
|
||||||
|
| From Email | Adres nadawcy (wymagany) |
|
||||||
|
| To | Odbiorcy (przecinkami) |
|
||||||
|
| Subject | Temat (wymagany) |
|
||||||
|
| Markdown | Treść w Markdown (wymagany) |
|
||||||
|
| Theme | Motyw: default / light / dark |
|
||||||
|
| Theme Overrides | Nadpisanie kolorów i fontów |
|
||||||
|
| Attachments | Nazwy binary properties |
|
||||||
|
|
||||||
|
## Przykładowy Markdown
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
preheader: Krótki podgląd w skrzynce odbiorczej
|
||||||
|
---
|
||||||
|
|
||||||
|
# Witaj {{$json.name}}!
|
||||||
|
|
||||||
|
Dziękujemy za rejestrację. Twoje konto jest gotowe.
|
||||||
|
|
||||||
|
:::callout
|
||||||
|
Twój kod aktywacyjny: **{{$json.code}}**
|
||||||
|
:::
|
||||||
|
|
||||||
|
[Aktywuj konto](https://example.com/activate){button}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Jeśli masz pytania, odpowiedz na tego emaila.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Obsługiwana składnia emailmd
|
||||||
|
|
||||||
|
- **Przyciski**: `[tekst](url){button}` lub `{button .success}`, `{button .danger}`
|
||||||
|
- **Callout**: `:::callout ... :::`
|
||||||
|
- **Hero**: `:::hero ... :::`
|
||||||
|
- **Header/Footer**: `:::header ... :::`, `:::footer ... :::`
|
||||||
|
- **Wyrównanie**: `:::centered ... :::`
|
||||||
|
- **Tabele**, listy, kod, obrazy — standardowy Markdown
|
||||||
44
credentials/SmtpCredential.credentials.ts
Normal file
44
credentials/SmtpCredential.credentials.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class SmtpCredential implements ICredentialType {
|
||||||
|
name = 'smtp';
|
||||||
|
displayName = 'SMTP';
|
||||||
|
documentationUrl = 'https://docs.n8n.io/integrations/builtin/credentials/sendemail/';
|
||||||
|
properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Host',
|
||||||
|
name: 'host',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'smtp.example.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Port',
|
||||||
|
name: 'port',
|
||||||
|
type: 'number',
|
||||||
|
default: 465,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Secure',
|
||||||
|
name: 'secure',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description: 'Whether to use SSL/TLS',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'User',
|
||||||
|
name: 'user',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Password',
|
||||||
|
name: 'password',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
7
gulpfile.js
Normal file
7
gulpfile.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const { src, dest } = require('gulp');
|
||||||
|
|
||||||
|
function copyIcons() {
|
||||||
|
return src('nodes/**/*.{png,svg}').pipe(dest('dist/nodes'));
|
||||||
|
}
|
||||||
|
|
||||||
|
exports['build:icons'] = copyIcons;
|
||||||
312
nodes/EmailMd/EmailMd.node.ts
Normal file
312
nodes/EmailMd/EmailMd.node.ts
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
import type {
|
||||||
|
IExecuteFunctions,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
|
||||||
|
import { render } from 'emailmd';
|
||||||
|
import nodemailer from 'nodemailer';
|
||||||
|
import type Mail from 'nodemailer/lib/mailer';
|
||||||
|
|
||||||
|
export class EmailMd implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'Send Email (Markdown)',
|
||||||
|
name: 'emailMd',
|
||||||
|
icon: 'fa:envelope',
|
||||||
|
group: ['output'],
|
||||||
|
version: 1,
|
||||||
|
description: 'Renders a Markdown template with emailmd and sends it via SMTP',
|
||||||
|
defaults: {
|
||||||
|
name: 'Send Email (Markdown)',
|
||||||
|
},
|
||||||
|
inputs: [NodeConnectionTypes.Main],
|
||||||
|
outputs: [NodeConnectionTypes.Main],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'smtp',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
// ─── Recipients ────────────────────────────────────────────────────
|
||||||
|
{
|
||||||
|
displayName: 'From Name',
|
||||||
|
name: 'fromName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'John Doe',
|
||||||
|
description: 'Sender display name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'From Email',
|
||||||
|
name: 'fromEmail',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'sender@example.com',
|
||||||
|
description: 'Sender email address',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'To',
|
||||||
|
name: 'toEmail',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'recipient@example.com',
|
||||||
|
description: 'Comma-separated list of recipients',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Subject',
|
||||||
|
name: 'subject',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'Hello from n8n!',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Reply To',
|
||||||
|
name: 'replyTo',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'reply@example.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'CC',
|
||||||
|
name: 'cc',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'cc@example.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'BCC',
|
||||||
|
name: 'bcc',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'bcc@example.com',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ─── Markdown Content ───────────────────────────────────────────────
|
||||||
|
{
|
||||||
|
displayName: 'Markdown',
|
||||||
|
name: 'markdown',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
rows: 20,
|
||||||
|
},
|
||||||
|
default: `---
|
||||||
|
preheader: Preview text shown in email clients
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hello {{name}}!
|
||||||
|
|
||||||
|
Welcome to our service. Here's what you need to know.
|
||||||
|
|
||||||
|
[Get Started](https://example.com){button}
|
||||||
|
`,
|
||||||
|
description:
|
||||||
|
'Markdown content to render. Supports YAML frontmatter for preheader and theme settings. Expressions like {{$json.name}} are evaluated before rendering.',
|
||||||
|
required: true,
|
||||||
|
noDataExpression: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ─── Theme Options ──────────────────────────────────────────────────
|
||||||
|
{
|
||||||
|
displayName: 'Theme',
|
||||||
|
name: 'theme',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{ name: 'Default', value: 'default' },
|
||||||
|
{ name: 'Light', value: 'light' },
|
||||||
|
{ name: 'Dark', value: 'dark' },
|
||||||
|
],
|
||||||
|
default: 'default',
|
||||||
|
description: 'Base theme for the email',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Theme Overrides',
|
||||||
|
name: 'themeOverrides',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Override',
|
||||||
|
default: {},
|
||||||
|
description: 'Custom theme color and typography overrides',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Brand Color',
|
||||||
|
name: 'brandColor',
|
||||||
|
type: 'color',
|
||||||
|
default: '#1a56db',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Background Color',
|
||||||
|
name: 'backgroundColor',
|
||||||
|
type: 'color',
|
||||||
|
default: '#f4f4f5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Content Background',
|
||||||
|
name: 'contentBackground',
|
||||||
|
type: 'color',
|
||||||
|
default: '#ffffff',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Text Color',
|
||||||
|
name: 'bodyColor',
|
||||||
|
type: 'color',
|
||||||
|
default: '#374151',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Heading Color',
|
||||||
|
name: 'headingColor',
|
||||||
|
type: 'color',
|
||||||
|
default: '#111827',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Font Family',
|
||||||
|
name: 'fontFamily',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'Arial, sans-serif',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Content Width',
|
||||||
|
name: 'contentWidth',
|
||||||
|
type: 'string',
|
||||||
|
default: '600px',
|
||||||
|
placeholder: '600px',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// ─── Advanced ───────────────────────────────────────────────────────
|
||||||
|
{
|
||||||
|
displayName: 'Attachments',
|
||||||
|
name: 'attachments',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'Comma-separated list of binary property names that contain the attachment data',
|
||||||
|
placeholder: 'attachment1, attachment2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
const items = this.getInputData();
|
||||||
|
const returnData: INodeExecutionData[] = [];
|
||||||
|
|
||||||
|
const credentials = await this.getCredentials('smtp');
|
||||||
|
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
host: credentials.host as string,
|
||||||
|
port: credentials.port as number,
|
||||||
|
secure: credentials.secure as boolean,
|
||||||
|
auth: {
|
||||||
|
user: credentials.user as string,
|
||||||
|
pass: credentials.password as string,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
try {
|
||||||
|
const fromName = this.getNodeParameter('fromName', i) as string;
|
||||||
|
const fromEmail = this.getNodeParameter('fromEmail', i) as string;
|
||||||
|
const toEmail = this.getNodeParameter('toEmail', i) as string;
|
||||||
|
const subject = this.getNodeParameter('subject', i) as string;
|
||||||
|
const replyTo = this.getNodeParameter('replyTo', i) as string;
|
||||||
|
const cc = this.getNodeParameter('cc', i) as string;
|
||||||
|
const bcc = this.getNodeParameter('bcc', i) as string;
|
||||||
|
const markdown = this.getNodeParameter('markdown', i) as string;
|
||||||
|
const theme = this.getNodeParameter('theme', i) as string;
|
||||||
|
const themeOverrides = this.getNodeParameter('themeOverrides', i) as Record<string, string>;
|
||||||
|
const attachmentProperties = this.getNodeParameter('attachments', i) as string;
|
||||||
|
|
||||||
|
// Build theme object
|
||||||
|
const themeOptions: Record<string, string> = {};
|
||||||
|
if (theme !== 'default') {
|
||||||
|
themeOptions.theme = theme;
|
||||||
|
}
|
||||||
|
for (const [key, value] of Object.entries(themeOverrides)) {
|
||||||
|
if (value) {
|
||||||
|
themeOptions[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render markdown -> HTML + plain text
|
||||||
|
let rendered: { html: string; text: string; meta: Record<string, unknown> };
|
||||||
|
try {
|
||||||
|
rendered = render(markdown, {
|
||||||
|
theme: Object.keys(themeOptions).length > 0 ? themeOptions : undefined,
|
||||||
|
});
|
||||||
|
} catch (renderError) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`emailmd render error: ${(renderError as Error).message}`,
|
||||||
|
{ itemIndex: i },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve subject from meta preheader if not set, or use provided
|
||||||
|
const resolvedSubject = subject || (rendered.meta?.subject as string) || '(no subject)';
|
||||||
|
|
||||||
|
// Build from address
|
||||||
|
const from = fromName ? `"${fromName}" <${fromEmail}>` : fromEmail;
|
||||||
|
|
||||||
|
// Handle attachments
|
||||||
|
const attachments: Mail.Attachment[] = [];
|
||||||
|
if (attachmentProperties.trim()) {
|
||||||
|
const propNames = attachmentProperties.split(',').map((p) => p.trim()).filter(Boolean);
|
||||||
|
for (const propName of propNames) {
|
||||||
|
const binaryData = this.helpers.assertBinaryData(i, propName);
|
||||||
|
const binaryBuffer = await this.helpers.getBinaryDataBuffer(i, propName);
|
||||||
|
attachments.push({
|
||||||
|
filename: binaryData.fileName || propName,
|
||||||
|
content: binaryBuffer,
|
||||||
|
contentType: binaryData.mimeType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send email
|
||||||
|
const mailOptions: Mail.Options = {
|
||||||
|
from,
|
||||||
|
to: toEmail,
|
||||||
|
subject: resolvedSubject,
|
||||||
|
html: rendered.html,
|
||||||
|
text: rendered.text,
|
||||||
|
...(replyTo && { replyTo }),
|
||||||
|
...(cc && { cc }),
|
||||||
|
...(bcc && { bcc }),
|
||||||
|
...(attachments.length > 0 && { attachments }),
|
||||||
|
};
|
||||||
|
|
||||||
|
const info = await transporter.sendMail(mailOptions);
|
||||||
|
|
||||||
|
returnData.push({
|
||||||
|
json: {
|
||||||
|
messageId: info.messageId,
|
||||||
|
accepted: info.accepted,
|
||||||
|
rejected: info.rejected,
|
||||||
|
pending: info.pending,
|
||||||
|
response: info.response,
|
||||||
|
subject: resolvedSubject,
|
||||||
|
to: toEmail,
|
||||||
|
from,
|
||||||
|
},
|
||||||
|
pairedItem: { item: i },
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (this.continueOnFail()) {
|
||||||
|
returnData.push({
|
||||||
|
json: { error: (error as Error).message },
|
||||||
|
pairedItem: { item: i },
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [returnData];
|
||||||
|
}
|
||||||
|
}
|
||||||
9445
package-lock.json
generated
Normal file
9445
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
59
package.json
Normal file
59
package.json
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"name": "n8n-nodes-emailmd",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "n8n node for sending emails rendered from Markdown using emailmd",
|
||||||
|
"keywords": [
|
||||||
|
"n8n-community-node-package",
|
||||||
|
"n8n",
|
||||||
|
"email",
|
||||||
|
"markdown",
|
||||||
|
"emailmd"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "https://github.com/unmta/emailmd",
|
||||||
|
"author": {
|
||||||
|
"name": "n8n-nodes-emailmd"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/unmta/emailmd.git"
|
||||||
|
},
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc && gulp build:icons",
|
||||||
|
"dev": "tsc --watch",
|
||||||
|
"format": "prettier nodes --write",
|
||||||
|
"lint": "eslint nodes --ext .ts",
|
||||||
|
"lintfix": "eslint nodes --ext .ts --fix",
|
||||||
|
"prepublishOnly": "npm run build && npm run lint -c .eslintrc.prepublish.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"n8n": {
|
||||||
|
"n8nNodesApiVersion": 1,
|
||||||
|
"credentials": [
|
||||||
|
"dist/credentials/SmtpCredential.credentials.js"
|
||||||
|
],
|
||||||
|
"nodes": [
|
||||||
|
"dist/nodes/EmailMd/EmailMd.node.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/nodemailer": "^6.4.17",
|
||||||
|
"@typescript-eslint/parser": "^5.62.0",
|
||||||
|
"eslint-plugin-n8n-nodes-base": "^1.16.2",
|
||||||
|
"gulp": "^4.0.2",
|
||||||
|
"n8n-workflow": "*",
|
||||||
|
"prettier": "^3.3.3",
|
||||||
|
"typescript": "^5.6.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"emailmd": "^0.1.0",
|
||||||
|
"nodemailer": "^6.9.16"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0",
|
||||||
|
"npm": ">=8.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
tsconfig.json
Normal file
19
tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"target": "es2019",
|
||||||
|
"lib": ["es2019", "es2020.promise", "es2020.bigint", "es2020.string"],
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./",
|
||||||
|
"typeRoots": ["./node_modules/@types"],
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": ["credentials/**/*.ts", "nodes/**/*.ts"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user