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.
- package/package.json +1 -1
- package/src/sender.js +105 -68
package/package.json
CHANGED
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
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
523
|
-
|
|
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
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
|
|
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
|
-
|
|
550
|
-
if (
|
|
551
|
-
|
|
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
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
break;
|
|
567
|
-
}
|
|
601
|
+
if (configManager.config.configurations.multiple_senders === true && sender) {
|
|
602
|
+
senderRanker.updateSenderSuccess(sender, result.success);
|
|
603
|
+
}
|
|
568
604
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|