n8n-nodes-gmail-custom 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -16
- package/nodes/GmailCustom/GmailCustom.node.js +51 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,32 +1,66 @@
|
|
|
1
1
|
# n8n-nodes-gmail-custom
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Self-contained n8n node for sending emails via Gmail API with a Google service account and domain-wide delegation.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
No credential setup in n8n required — all parameters are inline, supports expressions.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
- **Token caching
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
-
|
|
9
|
+
- **Self-contained**: service account email + private key are node parameters (no n8n credential needed)
|
|
10
|
+
- **Token caching**: access token reused for 58 minutes — only 1 oauth call per hour instead of per email
|
|
11
|
+
- **Domain-wide delegation**: send as any user in your Workspace domain
|
|
12
|
+
- **Sender name**: set a display name without extra API calls
|
|
13
|
+
- **Optional custom Message-ID**: auto-generated `<uuid@domain>` or specify your own — included in output
|
|
14
|
+
- **Attachments**: from binary data with graceful skip if missing
|
|
15
|
+
- **HTML and plain text email support**
|
|
14
16
|
|
|
15
17
|
## Installation
|
|
16
18
|
|
|
17
|
-
In n8n: **Settings → Community Nodes → Install** →
|
|
19
|
+
In n8n: **Settings → Community Nodes → Install** → `n8n-nodes-gmail-custom`
|
|
18
20
|
|
|
19
21
|
## Usage
|
|
20
22
|
|
|
21
23
|
1. Add a **Gmail Custom** node to your workflow
|
|
22
|
-
2.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
2. Fill in the required fields (supports expressions):
|
|
25
|
+
|
|
26
|
+
| Field | Description | Example |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| Service Account Email | Your SA `client_email` | `sa-name@project.iam.gserviceaccount.com` |
|
|
29
|
+
| Private Key | RSA private key (PEM, hidden) | Expression from a previous node |
|
|
30
|
+
| From Email | The user to impersonate (DWD) | `sender@your-domain.com` |
|
|
31
|
+
| To | Recipient(s), comma-separated | `recipient@example.com` |
|
|
32
|
+
| Subject | Email subject | `Hello World` |
|
|
33
|
+
| Email Type | `HTML` or `Text` | `HTML` |
|
|
34
|
+
| Message | Email body | Your HTML or text |
|
|
35
|
+
|
|
36
|
+
3. Configure **Options** as needed:
|
|
37
|
+
- **CC / BCC**: carbon copy recipients
|
|
38
|
+
- **Reply To**: reply-to address
|
|
39
|
+
- **Sender Name**: display name (e.g. `John Doe`)
|
|
40
|
+
- **Attachments**: binary data field names from previous nodes
|
|
41
|
+
- **Custom Message-ID**: enable and optionally specify a `Message-ID` header. Auto-generated as `<uuid@domain>` if left empty. Returned in the node output.
|
|
42
|
+
|
|
43
|
+
## Output
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"id": "19ef486e4ed521e6",
|
|
48
|
+
"threadId": "19ef486e4ed521e6",
|
|
49
|
+
"labelIds": ["SENT"],
|
|
50
|
+
"messageId": "<generated-uuid@your-domain.com>"
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`messageId` is only present when the Custom Message-ID option is enabled.
|
|
55
|
+
|
|
56
|
+
## How it works
|
|
57
|
+
|
|
58
|
+
1. Signs a JWT with the service account private key
|
|
59
|
+
2. Obtains an access token from `oauth2.googleapis.com/token` (cached)
|
|
60
|
+
3. Builds a MIME email message
|
|
61
|
+
4. Sends via `POST gmail.googleapis.com/gmail/v1/users/me/messages/send`
|
|
62
|
+
|
|
63
|
+
Per email sent: **1 Gmail API call** (token is cached for 1 hour).
|
|
30
64
|
|
|
31
65
|
## License
|
|
32
66
|
|
|
@@ -51,6 +51,10 @@ async function buildMimeMessage(options) {
|
|
|
51
51
|
mailOptions.html = options.htmlBody;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
if (options.messageId) {
|
|
55
|
+
mailOptions.messageId = options.messageId;
|
|
56
|
+
}
|
|
57
|
+
|
|
54
58
|
if (options.attachments && options.attachments.length > 0) {
|
|
55
59
|
mailOptions.attachments = options.attachments.map((att) => ({
|
|
56
60
|
filename: att.fileName || 'attachment',
|
|
@@ -89,6 +93,7 @@ async function buildMimeMessage(options) {
|
|
|
89
93
|
if (cc) lines.push(`CC: ${cc}`);
|
|
90
94
|
if (bcc) lines.push(`BCC: ${bcc}`);
|
|
91
95
|
if (replyTo) lines.push(`Reply-To: ${replyTo}`);
|
|
96
|
+
if (options.messageId) lines.push(`Message-ID: ${options.messageId}`);
|
|
92
97
|
lines.push(`Subject: ${subject}`);
|
|
93
98
|
lines.push('MIME-Version: 1.0');
|
|
94
99
|
|
|
@@ -363,6 +368,25 @@ class GmailCustom {
|
|
|
363
368
|
placeholder: 'e.g. John Doe',
|
|
364
369
|
description: 'Name shown as the sender in recipients\' inboxes',
|
|
365
370
|
},
|
|
371
|
+
{
|
|
372
|
+
displayName: 'Custom Message-ID',
|
|
373
|
+
name: 'enableMessageId',
|
|
374
|
+
type: 'boolean',
|
|
375
|
+
default: false,
|
|
376
|
+
description: 'Whether to set a custom Message-ID header (auto-generated if not specified)',
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
displayName: 'Message-ID Value',
|
|
380
|
+
name: 'customMessageId',
|
|
381
|
+
type: 'string',
|
|
382
|
+
default: '',
|
|
383
|
+
displayOptions: {
|
|
384
|
+
show: {
|
|
385
|
+
enableMessageId: [true],
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
description: 'Leave empty to auto-generate a unique Message-ID',
|
|
389
|
+
},
|
|
366
390
|
],
|
|
367
391
|
},
|
|
368
392
|
],
|
|
@@ -404,6 +428,16 @@ class GmailCustom {
|
|
|
404
428
|
fromHeader = `${senderName} <${fromEmail}>`;
|
|
405
429
|
}
|
|
406
430
|
|
|
431
|
+
let messageId = null;
|
|
432
|
+
if (options.enableMessageId) {
|
|
433
|
+
if (options.customMessageId) {
|
|
434
|
+
messageId = options.customMessageId;
|
|
435
|
+
} else {
|
|
436
|
+
const domain = fromEmail.includes('@') ? fromEmail.split('@')[1] : 'localhost';
|
|
437
|
+
messageId = `<${crypto.randomUUID()}@${domain}>`;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
407
441
|
let bodyText = message;
|
|
408
442
|
let bodyHtml = '';
|
|
409
443
|
|
|
@@ -450,22 +484,27 @@ class GmailCustom {
|
|
|
450
484
|
textBody: bodyText,
|
|
451
485
|
htmlBody: bodyHtml,
|
|
452
486
|
attachments,
|
|
487
|
+
messageId,
|
|
453
488
|
});
|
|
454
489
|
|
|
455
490
|
const sendResponse = await this.helpers.httpRequest({
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
491
|
+
method: 'POST',
|
|
492
|
+
url: 'https://gmail.googleapis.com/gmail/v1/users/me/messages/send',
|
|
493
|
+
headers: {
|
|
494
|
+
Authorization: `Bearer ${accessToken}`,
|
|
495
|
+
'Content-Type': 'application/json',
|
|
496
|
+
},
|
|
497
|
+
body: { raw: base64UrlMessage },
|
|
498
|
+
});
|
|
464
499
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
500
|
+
const result = { ...sendResponse };
|
|
501
|
+
if (messageId) {
|
|
502
|
+
result.messageId = messageId;
|
|
503
|
+
}
|
|
504
|
+
returnData.push({
|
|
505
|
+
json: result,
|
|
506
|
+
pairedItem: { item: i },
|
|
507
|
+
});
|
|
469
508
|
|
|
470
509
|
} catch (error) {
|
|
471
510
|
let continueOnFail = false;
|