nolimit-x 1.0.80 → 1.0.82

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/sender.js +105 -68
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nolimit-x",
3
- "version": "1.0.80",
3
+ "version": "1.0.82",
4
4
  "description": "Advanced email sender ",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
package/src/sender.js CHANGED
@@ -485,96 +485,133 @@ async function send(options) {
485
485
  }
486
486
 
487
487
 
488
- if (_smtpRotation) {
489
- for (let i = 0; i < allJobs.length; i++) {
490
- const rotatedSmtp = _smtpArr[i % _smtpArr.length];
491
- allJobs[i].smtp = [rotatedSmtp]; // Rust reads smtp[0]
492
- allJobs[i].from = rotatedSmtp.user; // envelope sender matches SMTP auth
493
- }
494
- }
495
-
496
-
497
488
  let totalSent = 0;
498
489
 
499
490
  if (allJobs.length > 0) {
500
491
  console.log('');
501
492
 
502
- // Health-aware SMTP rotation: send detect dead SMTPs → retry failed through healthy ones
503
- const deadSmtps = new Set(); // permanently failed (535 auth)
504
- const ratelimited = new Set(); // temporarily exhausted (554 rate limit)
505
- let pendingJobs = allJobs;
506
- let pendingEmails = allEmails;
507
- let retryRound = 0;
508
- const MAX_RETRIES = 2;
493
+ if (_smtpRotation && _smtpArr.length > 1) {
494
+ // ─── CHUNKED HEALTH-AWARE SENDING ───
495
+ // Send in sub-batches (1 email per SMTP per chunk).
496
+ // After each chunk, detect dead/rate-limited SMTPs and remove them.
497
+ // Dead SMTPs waste at most 1 email instead of hundreds.
498
+ const deadSmtps = new Set();
499
+ const rateLimited = new Set();
500
+ let healthyPool = [..._smtpArr];
501
+ let jobQueue = allJobs.map((job, idx) => ({ job, email: allEmails[idx] }));
502
+ const retryQueue = []; // failed jobs to retry through healthy SMTPs
503
+
504
+ while (jobQueue.length > 0 && healthyPool.length > 0) {
505
+ // Build one chunk: assign 1 job per healthy SMTP
506
+ const chunkSize = healthyPool.length;
507
+ const chunk = jobQueue.splice(0, chunkSize);
508
+
509
+ // Assign each job in the chunk to a different SMTP
510
+ for (let i = 0; i < chunk.length; i++) {
511
+ const smtp = healthyPool[i % healthyPool.length];
512
+ chunk[i].job.smtp = [smtp];
513
+ chunk[i].job.from = smtp.user;
514
+ }
509
515
 
510
- while (pendingJobs.length > 0) {
511
- const results = await sendEmailViaRust(pendingJobs, useRawSmtp);
512
- const retryJobs = [];
513
- const retryEmails = [];
516
+ // Send the chunk
517
+ const chunkJobs = chunk.map(c => c.job);
518
+ const results = await sendEmailViaRust(chunkJobs, useRawSmtp);
514
519
 
515
- for (let i = 0; i < results.length; i++) {
516
- const result = results[i];
517
- const email = pendingEmails[i];
518
- const job = pendingJobs[i];
519
- const sender = job.from || job.from_email;
520
- const smtpUser = job.smtp?.[0]?.user || '';
520
+ // Process results and track health
521
+ for (let i = 0; i < results.length; i++) {
522
+ const result = results[i];
523
+ const { job, email } = chunk[i];
524
+ const sender = job.from || job.from_email;
525
+ const smtpUser = job.smtp?.[0]?.user || '';
521
526
 
522
- if (configManager.config.configurations.multiple_senders === true && sender) {
523
- senderRanker.updateSenderSuccess(sender, result.success);
527
+ if (configManager.config.configurations.multiple_senders === true && sender) {
528
+ senderRanker.updateSenderSuccess(sender, result.success);
529
+ }
530
+
531
+ if (result.success) {
532
+ campaign.successfulSends++;
533
+ totalSent++;
534
+ try { senderIntel.recordSuccessfulSend(campaignId, sender, email, result); } catch (e) { }
535
+ } else {
536
+ const errMsg = (result.error || result.message || '').toLowerCase();
537
+ if (errMsg.includes('535') || errMsg.includes('authentication failed')) {
538
+ deadSmtps.add(smtpUser);
539
+ retryQueue.push({ job, email }); // re-queue for healthy SMTP
540
+ } else if (errMsg.includes('554') || errMsg.includes('limit') || errMsg.includes('smtp auth error limit')) {
541
+ rateLimited.add(smtpUser);
542
+ retryQueue.push({ job, email }); // re-queue for healthy SMTP
543
+ } else {
544
+ // Genuine failure (bad recipient, DNS, etc.) — don't retry
545
+ campaign.failedSends++;
546
+ try { senderIntel.recordFailedSend(campaignId, sender, email, result); } catch (e) { }
547
+ }
548
+ }
549
+ campaign.processedEmails++;
524
550
  }
525
551
 
526
- if (result.success) {
527
- campaign.successfulSends++;
528
- totalSent++;
529
- try { senderIntel.recordSuccessfulSend(campaignId, sender, email, result); } catch (e) { }
530
- } else {
531
- const errMsg = (result.error || result.message || '').toLowerCase();
532
- if (errMsg.includes('535') || errMsg.includes('authentication failed')) {
533
- deadSmtps.add(smtpUser);
534
- retryJobs.push(job);
535
- retryEmails.push(email);
536
- } else if (errMsg.includes('554') || errMsg.includes('limit')) {
537
- ratelimited.add(smtpUser);
538
- retryJobs.push(job);
539
- retryEmails.push(email);
552
+ // Prune dead and rate-limited SMTPs from pool
553
+ const beforeCount = healthyPool.length;
554
+ healthyPool = healthyPool.filter(s => !deadSmtps.has(s.user) && !rateLimited.has(s.user));
555
+
556
+ if (healthyPool.length < beforeCount && healthyPool.length > 0) {
557
+ console.log(`${YELLOW}⚠ ${beforeCount - healthyPool.length} SMTP(s) removed · ${healthyPool.length} healthy remaining${RESET}`);
558
+ }
559
+ }
560
+
561
+ // Retry queue: send failed emails through surviving healthy SMTPs
562
+ if (retryQueue.length > 0 && healthyPool.length > 0) {
563
+ for (let i = 0; i < retryQueue.length; i++) {
564
+ const smtp = healthyPool[i % healthyPool.length];
565
+ retryQueue[i].job.smtp = [smtp];
566
+ retryQueue[i].job.from = smtp.user;
567
+ }
568
+ const retryResults = await sendEmailViaRust(retryQueue.map(r => r.job), useRawSmtp);
569
+ for (let i = 0; i < retryResults.length; i++) {
570
+ const result = retryResults[i];
571
+ const { job, email } = retryQueue[i];
572
+ const sender = job.from || job.from_email;
573
+ if (result.success) {
574
+ campaign.successfulSends++;
575
+ totalSent++;
576
+ try { senderIntel.recordSuccessfulSend(campaignId, sender, email, result); } catch (e) { }
540
577
  } else {
541
- // Genuine send failure — don't retry
542
578
  campaign.failedSends++;
543
579
  try { senderIntel.recordFailedSend(campaignId, sender, email, result); } catch (e) { }
544
580
  }
581
+ campaign.processedEmails++;
545
582
  }
546
- campaign.processedEmails++;
583
+ } else if (retryQueue.length > 0) {
584
+ // No healthy SMTPs left — mark all retries as failed
585
+ campaign.failedSends += retryQueue.length;
547
586
  }
548
587
 
549
- retryRound++;
550
- if (retryRound > MAX_RETRIES || retryJobs.length === 0) {
551
- // Mark remaining retries as failed
552
- if (retryJobs.length > 0) {
553
- campaign.failedSends += retryJobs.length;
554
- }
555
- break;
588
+ // Any jobs left in queue (no healthy SMTPs broke the loop)
589
+ if (jobQueue.length > 0) {
590
+ campaign.failedSends += jobQueue.length;
556
591
  }
557
592
 
558
- // Healthy SMTPs = pool minus dead and ratelimited
559
- const healthySmtps = _smtpRotation
560
- ? _smtpArr.filter(s => !deadSmtps.has(s.user) && !ratelimited.has(s.user))
561
- : [];
593
+ } else {
594
+ // ─── SINGLE SMTP (no rotation) — send all at once ───
595
+ const results = await sendEmailViaRust(allJobs, useRawSmtp);
596
+ for (let i = 0; i < results.length; i++) {
597
+ const result = results[i];
598
+ const email = allEmails[i];
599
+ const sender = allJobs[i].from || allJobs[i].from_email;
562
600
 
563
- if (healthySmtps.length === 0) {
564
- // No healthy SMTPs left — mark all as failed
565
- campaign.failedSends += retryJobs.length;
566
- break;
567
- }
601
+ if (configManager.config.configurations.multiple_senders === true && sender) {
602
+ senderRanker.updateSenderSuccess(sender, result.success);
603
+ }
568
604
 
569
- // Reassign failed jobs to healthy SMTPs
570
- for (let i = 0; i < retryJobs.length; i++) {
571
- const smtp = healthySmtps[i % healthySmtps.length];
572
- retryJobs[i].smtp = [smtp];
573
- retryJobs[i].from = smtp.user;
605
+ if (result.success) {
606
+ campaign.successfulSends++;
607
+ totalSent++;
608
+ try { senderIntel.recordSuccessfulSend(campaignId, sender, email, result); } catch (e) { }
609
+ } else {
610
+ campaign.failedSends++;
611
+ try { senderIntel.recordFailedSend(campaignId, sender, email, result); } catch (e) { }
612
+ }
613
+ campaign.processedEmails++;
574
614
  }
575
-
576
- pendingJobs = retryJobs;
577
- pendingEmails = retryEmails;
578
615
  }
579
616
  }
580
617
  // End campaign