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.
- package/package.json +1 -1
- package/src/sender.js +118 -24
package/package.json
CHANGED
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
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
509
|
-
|
|
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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|