n8n-nodes-gmail-custom 0.2.0 → 0.3.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.
|
@@ -161,19 +161,19 @@ async function buildMimeMessage(options) {
|
|
|
161
161
|
.replace(/=+$/, '');
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
async function getOrRefreshAccessToken(ctx,
|
|
165
|
-
const cacheKey = `${
|
|
164
|
+
async function getOrRefreshAccessToken(ctx, serviceAccountEmail, privateKeyRaw, fromEmail) {
|
|
165
|
+
const cacheKey = `${serviceAccountEmail}:${fromEmail}`;
|
|
166
166
|
let token = TOKEN_CACHE[cacheKey];
|
|
167
167
|
if (token && token.expiresAt > Date.now()) {
|
|
168
168
|
return token.accessToken;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
const privateKey = formatPrivateKey(
|
|
171
|
+
const privateKey = formatPrivateKey(privateKeyRaw);
|
|
172
172
|
const now = Math.floor(Date.now() / 1000);
|
|
173
173
|
|
|
174
174
|
let jwt;
|
|
175
175
|
const payload = {
|
|
176
|
-
iss:
|
|
176
|
+
iss: serviceAccountEmail,
|
|
177
177
|
scope: 'https://mail.google.com/',
|
|
178
178
|
aud: 'https://oauth2.googleapis.com/token',
|
|
179
179
|
exp: now + 3600,
|
|
@@ -223,20 +223,32 @@ class GmailCustom {
|
|
|
223
223
|
group: ['output'],
|
|
224
224
|
version: 1,
|
|
225
225
|
subtitle: '={{$parameter["subject"] || "Send Email"}}',
|
|
226
|
-
description: 'Send email via Gmail API with token caching
|
|
226
|
+
description: 'Send email via Gmail API with token caching for service accounts',
|
|
227
227
|
defaults: {
|
|
228
228
|
name: 'Gmail Custom',
|
|
229
229
|
color: '#1a73e8',
|
|
230
230
|
},
|
|
231
231
|
inputs: ['main'],
|
|
232
232
|
outputs: ['main'],
|
|
233
|
-
|
|
233
|
+
properties: [
|
|
234
234
|
{
|
|
235
|
-
|
|
235
|
+
displayName: 'Service Account Email',
|
|
236
|
+
name: 'serviceAccountEmail',
|
|
237
|
+
type: 'string',
|
|
238
|
+
default: '',
|
|
236
239
|
required: true,
|
|
240
|
+
placeholder: 'sa-name@project.iam.gserviceaccount.com',
|
|
241
|
+
description: 'The service account client_email (issuer for JWT)',
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
displayName: 'Private Key',
|
|
245
|
+
name: 'privateKey',
|
|
246
|
+
type: 'string',
|
|
247
|
+
typeOptions: { password: true },
|
|
248
|
+
default: '',
|
|
249
|
+
required: true,
|
|
250
|
+
description: 'The service account RSA private key in PEM format',
|
|
237
251
|
},
|
|
238
|
-
],
|
|
239
|
-
properties: [
|
|
240
252
|
{
|
|
241
253
|
displayName: 'From Email',
|
|
242
254
|
name: 'fromEmail',
|
|
@@ -344,11 +356,12 @@ class GmailCustom {
|
|
|
344
356
|
description: 'The email address that the reply message is sent to',
|
|
345
357
|
},
|
|
346
358
|
{
|
|
347
|
-
displayName: '
|
|
348
|
-
name: '
|
|
349
|
-
type: '
|
|
350
|
-
default:
|
|
351
|
-
|
|
359
|
+
displayName: 'Sender Name',
|
|
360
|
+
name: 'senderName',
|
|
361
|
+
type: 'string',
|
|
362
|
+
default: '',
|
|
363
|
+
placeholder: 'e.g. John Doe',
|
|
364
|
+
description: 'Name shown as the sender in recipients\' inboxes',
|
|
352
365
|
},
|
|
353
366
|
],
|
|
354
367
|
},
|
|
@@ -367,6 +380,8 @@ class GmailCustom {
|
|
|
367
380
|
for (let i = 0; i < items.length; i++) {
|
|
368
381
|
try {
|
|
369
382
|
const item = items[i];
|
|
383
|
+
const serviceAccountEmail = this.getNodeParameter('serviceAccountEmail', i, '');
|
|
384
|
+
const privateKey = this.getNodeParameter('privateKey', i, '');
|
|
370
385
|
const fromEmail = this.getNodeParameter('fromEmail', i, '');
|
|
371
386
|
const sendTo = this.getNodeParameter('sendTo', i, '');
|
|
372
387
|
const subject = this.getNodeParameter('subject', i, '');
|
|
@@ -382,7 +397,12 @@ class GmailCustom {
|
|
|
382
397
|
let cc = options.ccList || '';
|
|
383
398
|
let bcc = options.bccList || '';
|
|
384
399
|
let replyTo = options.replyTo || '';
|
|
385
|
-
const
|
|
400
|
+
const senderName = options.senderName || '';
|
|
401
|
+
|
|
402
|
+
let fromHeader = fromEmail;
|
|
403
|
+
if (senderName) {
|
|
404
|
+
fromHeader = `${senderName} <${fromEmail}>`;
|
|
405
|
+
}
|
|
386
406
|
|
|
387
407
|
let bodyText = message;
|
|
388
408
|
let bodyHtml = '';
|
|
@@ -395,16 +415,7 @@ class GmailCustom {
|
|
|
395
415
|
bodyText = message;
|
|
396
416
|
}
|
|
397
417
|
|
|
398
|
-
|
|
399
|
-
if (emailType === 'html') {
|
|
400
|
-
bodyHtml += '<br><br>---<br><em>This email was sent automatically with n8n</em>';
|
|
401
|
-
} else {
|
|
402
|
-
bodyText += '\n\n---\nThis email was sent automatically with n8n';
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const credentials = await this.getCredentials('googleApi');
|
|
407
|
-
const accessToken = await getOrRefreshAccessToken(this, credentials, fromEmail);
|
|
418
|
+
const accessToken = await getOrRefreshAccessToken(this, serviceAccountEmail, privateKey, fromEmail);
|
|
408
419
|
|
|
409
420
|
let attachments = [];
|
|
410
421
|
if (options.attachmentsUi && options.attachmentsUi.attachmentsBinary) {
|
|
@@ -430,7 +441,7 @@ class GmailCustom {
|
|
|
430
441
|
}
|
|
431
442
|
|
|
432
443
|
const base64UrlMessage = await buildMimeMessage({
|
|
433
|
-
from:
|
|
444
|
+
from: fromHeader,
|
|
434
445
|
to: sendTo,
|
|
435
446
|
cc,
|
|
436
447
|
bcc,
|
|
@@ -441,40 +452,20 @@ class GmailCustom {
|
|
|
441
452
|
attachments,
|
|
442
453
|
});
|
|
443
454
|
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
Authorization: `Bearer ${accessToken}`,
|
|
454
|
-
'Content-Type': 'application/json',
|
|
455
|
-
},
|
|
456
|
-
body: { raw: base64UrlMessage },
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
returnData.push({
|
|
460
|
-
json: sendResponse,
|
|
461
|
-
pairedItem: { item: i },
|
|
462
|
-
});
|
|
463
|
-
lastError = null;
|
|
464
|
-
break;
|
|
465
|
-
} catch (error) {
|
|
466
|
-
lastError = error;
|
|
467
|
-
const statusCode = error.statusCode || error.response?.statusCode;
|
|
468
|
-
if (statusCode === 429 && attempt < maxRetries) {
|
|
469
|
-
const delay = Math.min(Math.pow(2, attempt) * 1000 + Math.random() * 1000, 30000);
|
|
470
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
throw error;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
455
|
+
const sendResponse = await this.helpers.httpRequest({
|
|
456
|
+
method: 'POST',
|
|
457
|
+
url: 'https://gmail.googleapis.com/gmail/v1/users/me/messages/send',
|
|
458
|
+
headers: {
|
|
459
|
+
Authorization: `Bearer ${accessToken}`,
|
|
460
|
+
'Content-Type': 'application/json',
|
|
461
|
+
},
|
|
462
|
+
body: { raw: base64UrlMessage },
|
|
463
|
+
});
|
|
476
464
|
|
|
477
|
-
|
|
465
|
+
returnData.push({
|
|
466
|
+
json: sendResponse,
|
|
467
|
+
pairedItem: { item: i },
|
|
468
|
+
});
|
|
478
469
|
|
|
479
470
|
} catch (error) {
|
|
480
471
|
let continueOnFail = false;
|