nolimit-x 1.0.79 → 1.0.81

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 +118 -24
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nolimit-x",
3
- "version": "1.0.79",
3
+ "version": "1.0.81",
4
4
  "description": "Advanced email sender ",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
package/src/sender.js CHANGED
@@ -485,39 +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
- const results = await sendEmailViaRust(allJobs, useRawSmtp);
502
492
 
503
- for (let i = 0; i < results.length; i++) {
504
- const result = results[i];
505
- const email = allEmails[i];
506
- const sender = allJobs[i].from || allJobs[i].from_email;
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
+ }
515
+
516
+ // Send the chunk
517
+ const chunkJobs = chunk.map(c => c.job);
518
+ const results = await sendEmailViaRust(chunkJobs, useRawSmtp);
519
+
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 || '';
526
+
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++;
550
+ }
551
+
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));
507
555
 
508
- if (configManager.config.configurations.multiple_senders === true && sender) {
509
- senderRanker.updateSenderSuccess(sender, result.success);
556
+ if (healthyPool.length < beforeCount && healthyPool.length > 0) {
557
+ console.log(`${YELLOW}⚠ ${beforeCount - healthyPool.length} SMTP(s) removed · ${healthyPool.length} healthy remaining${RESET}`);
558
+ }
510
559
  }
511
560
 
512
- if (result.success) {
513
- campaign.successfulSends++;
514
- totalSent++;
515
- try { senderIntel.recordSuccessfulSend(campaignId, sender, email, result); } catch (e) { }
516
- } else {
517
- campaign.failedSends++;
518
- try { senderIntel.recordFailedSend(campaignId, sender, email, result); } catch (e) { }
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) { }
577
+ } else {
578
+ campaign.failedSends++;
579
+ try { senderIntel.recordFailedSend(campaignId, sender, email, result); } catch (e) { }
580
+ }
581
+ campaign.processedEmails++;
582
+ }
583
+ } else if (retryQueue.length > 0) {
584
+ // No healthy SMTPs left — mark all retries as failed
585
+ campaign.failedSends += retryQueue.length;
586
+ }
587
+
588
+ // Any jobs left in queue (no healthy SMTPs broke the loop)
589
+ if (jobQueue.length > 0) {
590
+ campaign.failedSends += jobQueue.length;
591
+ }
592
+
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;
600
+
601
+ if (configManager.config.configurations.multiple_senders === true && sender) {
602
+ senderRanker.updateSenderSuccess(sender, result.success);
603
+ }
604
+
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++;
519
614
  }
520
- campaign.processedEmails++;
521
615
  }
522
616
  }
523
617
  // End campaign