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, credentials, fromEmail) {
165
- const cacheKey = `${credentials.email}:${fromEmail}`;
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(credentials.privateKey);
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: credentials.email,
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 and 429 retry',
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
- credentials: [
233
+ properties: [
234
234
  {
235
- name: 'googleApi',
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: 'Append n8n Attribution',
348
- name: 'appendAttribution',
349
- type: 'boolean',
350
- default: true,
351
- description: 'Whether to include the phrase "This email was sent automatically with n8n" to the end of the email',
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 appendAttribution = options.appendAttribution === undefined ? true : options.appendAttribution;
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
- if (appendAttribution) {
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: fromEmail,
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 maxRetries = 4;
445
- let lastError;
446
-
447
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
448
- try {
449
- const sendResponse = await this.helpers.httpRequest({
450
- method: 'POST',
451
- url: 'https://gmail.googleapis.com/gmail/v1/users/me/messages/send',
452
- headers: {
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
- if (lastError) throw lastError;
465
+ returnData.push({
466
+ json: sendResponse,
467
+ pairedItem: { item: i },
468
+ });
478
469
 
479
470
  } catch (error) {
480
471
  let continueOnFail = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-gmail-custom",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Custom Gmail node for n8n with token caching and 429 retry",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",