@emailcheck/email-validator-js 4.0.1 → 5.0.0-beta.1

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/dist/cli/index.js CHANGED
@@ -1052,15 +1052,21 @@ async function verifyMailboxSMTP(params) {
1052
1052
  const { local, domain, options = {} } = params;
1053
1053
  const mxRecords = params.mxRecords ?? [];
1054
1054
  const ports = (options.ports ?? DEFAULT_PORTS).filter((port) => Number.isInteger(port) && port > 0 && port < 65536);
1055
- const timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
1056
- const tlsConfig = options.tls ?? true;
1057
- const hostname = options.hostname ?? "localhost";
1055
+ const perAttemptTimeoutMs = options.perAttemptTimeoutMs ?? DEFAULT_TIMEOUT_MS;
1056
+ const tlsConfig = options.tlsConfig ?? true;
1057
+ const heloHostname = options.heloHostname ?? "localhost";
1058
1058
  const debug = options.debug ?? false;
1059
1059
  const captureTranscript = options.captureTranscript ?? false;
1060
1060
  const sequence = options.sequence;
1061
1061
  const cache = options.cache;
1062
1062
  const log = debug ? (...args) => console.log("[SMTP]", ...args) : () => {
1063
1063
  };
1064
+ const totalDeadlineMs = options.totalDeadlineMs;
1065
+ const maxConsecutiveFailures = options.maxConsecutiveFailures;
1066
+ const maxMxHosts = options.maxMxHosts;
1067
+ const retryAttempts = options.retry?.attempts ?? 0;
1068
+ const retryDelayMs = options.retry?.delayMs ?? 200;
1069
+ const retryBackoff = options.retry?.backoff ?? "exponential";
1064
1070
  const startedAtMs = Date.now();
1065
1071
  const primaryMx = mxRecords[0];
1066
1072
  if (!primaryMx) {
@@ -1074,9 +1080,9 @@ async function verifyMailboxSMTP(params) {
1074
1080
  const probeOptions = {
1075
1081
  local,
1076
1082
  domain,
1077
- timeout,
1083
+ perAttemptTimeoutMs,
1078
1084
  tlsConfig,
1079
- hostname,
1085
+ heloHostname,
1080
1086
  sequence,
1081
1087
  log,
1082
1088
  catchAllProbeLocal: options.catchAllProbeLocal,
@@ -1097,17 +1103,41 @@ async function verifyMailboxSMTP(params) {
1097
1103
  const mxHostsTried = [];
1098
1104
  let mxAttempts = 0;
1099
1105
  let portAttempts = 0;
1106
+ let consecutiveFailures = 0;
1100
1107
  let lastReason = "all_attempts_failed";
1101
1108
  let lastEnhancedStatus;
1102
1109
  let lastResponseCode;
1103
- for (const mxHost of mxRecords) {
1110
+ let stoppedEarly = null;
1111
+ const isConnectionFailure = (reason) => reason === "connection_error" || reason === "connection_timeout" || reason === "connection_closed" || reason === "socket_timeout";
1112
+ const retryDelayFor = (attemptIndex) => retryBackoff === "exponential" ? retryDelayMs * 2 ** (attemptIndex - 1) : retryDelayMs;
1113
+ const probeWithRetry = async (mxHost, port) => {
1114
+ let lastProbe = await runProbe({ ...probeOptions, mxHost, port });
1115
+ for (let i = 1; i <= retryAttempts; i++) {
1116
+ if (lastProbe.result !== null || !isConnectionFailure(lastProbe.reason)) break;
1117
+ const delay = retryDelayFor(i);
1118
+ log(`retry ${i}/${retryAttempts} on ${mxHost}:${port} after ${delay}ms (last: ${lastProbe.reason})`);
1119
+ await new Promise((r) => setTimeout(r, delay));
1120
+ portAttempts++;
1121
+ lastProbe = await runProbe({ ...probeOptions, mxHost, port });
1122
+ }
1123
+ return lastProbe;
1124
+ };
1125
+ outer: for (const mxHost of mxRecords) {
1126
+ if (maxMxHosts !== void 0 && mxAttempts >= maxMxHosts) {
1127
+ stoppedEarly = "max_mx_hosts";
1128
+ break;
1129
+ }
1104
1130
  mxHostsTried.push(mxHost);
1105
1131
  mxAttempts++;
1106
1132
  const portsForThisMx = mxHost === primaryMx && cachedPort ? [cachedPort, ...ports.filter((p) => p !== cachedPort)] : ports;
1107
1133
  for (const port of portsForThisMx) {
1134
+ if (totalDeadlineMs !== void 0 && Date.now() - startedAtMs >= totalDeadlineMs) {
1135
+ stoppedEarly = "deadline";
1136
+ break outer;
1137
+ }
1108
1138
  portAttempts++;
1109
1139
  log(`Testing ${mxHost}:${port}`);
1110
- const probe = await runProbe({ ...probeOptions, mxHost, port });
1140
+ const probe = await probeWithRetry(mxHost, port);
1111
1141
  collectTranscript(transcript, commands, probe, mxHost, port);
1112
1142
  lastReason = probe.reason;
1113
1143
  if (probe.enhancedStatus !== void 0) lastEnhancedStatus = probe.enhancedStatus;
@@ -1123,8 +1153,14 @@ async function verifyMailboxSMTP(params) {
1123
1153
  if (mxHost === primaryMx) await safeCacheSet(portCache, primaryMx, port);
1124
1154
  return { smtpResult: smtpResult2, cached: false, port, portCached: cachedPort === port };
1125
1155
  }
1156
+ consecutiveFailures = isConnectionFailure(probe.reason) ? consecutiveFailures + 1 : 0;
1157
+ if (maxConsecutiveFailures !== void 0 && consecutiveFailures >= maxConsecutiveFailures) {
1158
+ stoppedEarly = "consecutive_failures";
1159
+ break outer;
1160
+ }
1126
1161
  }
1127
1162
  }
1163
+ if (stoppedEarly) log(`Stopped early: ${stoppedEarly}`);
1128
1164
  log(`All MX\xD7port attempts failed (mx=${mxAttempts}, port=${portAttempts})`);
1129
1165
  const metrics = makeMetrics(mxHostsTried, mxAttempts, portAttempts, void 0, startedAtMs);
1130
1166
  const smtpResult = {
@@ -1275,16 +1311,16 @@ class SMTPProbeConnection {
1275
1311
  } else {
1276
1312
  this.socket = net__namespace.connect({ host: this.p.mxHost, port: this.p.port }, onConnect);
1277
1313
  }
1278
- this.socket.setTimeout(this.p.timeout, () => this.finish(null, "socket_timeout"));
1314
+ this.socket.setTimeout(this.p.perAttemptTimeoutMs, () => this.finish(null, "socket_timeout"));
1279
1315
  this.socket.on("error", () => this.finish(null, "connection_error"));
1280
1316
  this.socket.on("close", () => this.finish(null, "connection_closed"));
1281
1317
  }
1282
1318
  armConnectionTimer() {
1283
- this.connectionTimer = setTimeout(() => this.finish(null, "connection_timeout"), this.p.timeout);
1319
+ this.connectionTimer = setTimeout(() => this.finish(null, "connection_timeout"), this.p.perAttemptTimeoutMs);
1284
1320
  }
1285
1321
  resetStepTimer() {
1286
1322
  if (this.stepTimer) clearTimeout(this.stepTimer);
1287
- this.stepTimer = setTimeout(() => this.finish(null, "step_timeout"), this.p.timeout);
1323
+ this.stepTimer = setTimeout(() => this.finish(null, "step_timeout"), this.p.perAttemptTimeoutMs);
1288
1324
  }
1289
1325
  send(cmd) {
1290
1326
  if (this.resolved) return;
@@ -1309,10 +1345,10 @@ class SMTPProbeConnection {
1309
1345
  return;
1310
1346
  // server-driven; nothing to send
1311
1347
  case SMTPStep.ehlo:
1312
- this.send(`EHLO ${this.p.hostname}`);
1348
+ this.send(`EHLO ${this.p.heloHostname}`);
1313
1349
  return;
1314
1350
  case SMTPStep.helo:
1315
- this.send(`HELO ${this.p.hostname}`);
1351
+ this.send(`HELO ${this.p.heloHostname}`);
1316
1352
  return;
1317
1353
  case SMTPStep.startTls:
1318
1354
  this.executeStartTls();
@@ -1384,7 +1420,7 @@ class SMTPProbeConnection {
1384
1420
  this.supportsStartTls = false;
1385
1421
  this.supportsPipelining = false;
1386
1422
  this.postUpgradeReEhlo = true;
1387
- this.send(`EHLO ${this.p.hostname}`);
1423
+ this.send(`EHLO ${this.p.heloHostname}`);
1388
1424
  });
1389
1425
  this.socket = tlsSocket;
1390
1426
  this.socket.on("data", this.onData);
@@ -1392,7 +1428,7 @@ class SMTPProbeConnection {
1392
1428
  this.finish(null, this.tlsUpgrading ? "tls_handshake_failed" : "connection_error");
1393
1429
  });
1394
1430
  this.socket.on("close", () => this.finish(null, "connection_closed"));
1395
- this.socket.setTimeout(this.p.timeout, () => this.finish(null, "socket_timeout"));
1431
+ this.socket.setTimeout(this.p.perAttemptTimeoutMs, () => this.finish(null, "socket_timeout"));
1396
1432
  }
1397
1433
  /**
1398
1434
  * Send the dual-probe envelope (real RCPT + probe RCPT + RSET). Pipelined
@@ -2328,13 +2364,13 @@ async function runWhoisChecks(domain, params, result, skipWhois, log, collector)
2328
2364
  log(`[verifyEmail] WHOIS checks skipped for disposable: ${domain}`);
2329
2365
  return;
2330
2366
  }
2331
- const whoisTimeout = params.whoisTimeout ?? 5e3;
2367
+ const whoisTimeoutMs = params.whoisTimeoutMs ?? 5e3;
2332
2368
  const debug = params.debug ?? false;
2333
2369
  if (params.checkDomainAge) {
2334
2370
  try {
2335
2371
  result.domainAge = await collector.record(
2336
2372
  "whois-age",
2337
- () => getDomainAge(domain, whoisTimeout, debug, params.cache),
2373
+ () => getDomainAge(domain, whoisTimeoutMs, debug, params.cache),
2338
2374
  (info) => ({
2339
2375
  domain,
2340
2376
  found: info !== null,
@@ -2354,7 +2390,7 @@ async function runWhoisChecks(domain, params, result, skipWhois, log, collector)
2354
2390
  try {
2355
2391
  result.domainRegistration = await collector.record(
2356
2392
  "whois-registration",
2357
- () => getDomainRegistrationStatus(domain, whoisTimeout, debug, params.cache),
2393
+ () => getDomainRegistrationStatus(domain, whoisTimeoutMs, debug, params.cache),
2358
2394
  (info) => ({
2359
2395
  domain,
2360
2396
  found: info !== null,
@@ -2427,7 +2463,11 @@ async function runSmtp(local, domain, mxRecords, params, result, log, collector)
2427
2463
  options: {
2428
2464
  cache: params.cache,
2429
2465
  ports: resolveSmtpPorts(params.smtpPort, mxRecords[0]),
2430
- timeout: params.timeout ?? 4e3,
2466
+ perAttemptTimeoutMs: params.smtpPerAttemptTimeoutMs ?? 4e3,
2467
+ totalDeadlineMs: params.smtpTotalDeadlineMs,
2468
+ maxConsecutiveFailures: params.smtpMaxConsecutiveFailures,
2469
+ maxMxHosts: params.smtpMaxMxHosts,
2470
+ retry: params.smtpRetry,
2431
2471
  debug: params.debug ?? false,
2432
2472
  // Forward transcript capture so the SMTP step's details include
2433
2473
  // the full per-port transcript when the caller asked for it.
@@ -2613,7 +2653,7 @@ async function run(args, deps = {}) {
2613
2653
  emailAddress: args.email,
2614
2654
  verifyMx: args.verifyMx,
2615
2655
  verifySmtp: args.verifySmtp,
2616
- timeout: args.timeoutMs ?? 5e3,
2656
+ smtpPerAttemptTimeoutMs: args.timeoutMs ?? 5e3,
2617
2657
  debug: args.debug,
2618
2658
  smtpPort: args.smtpPort,
2619
2659
  checkDisposable: args.checkDisposable,
@@ -2622,7 +2662,7 @@ async function run(args, deps = {}) {
2622
2662
  suggestDomain: args.suggestDomain,
2623
2663
  checkDomainAge: args.checkDomainAge,
2624
2664
  checkDomainRegistration: args.checkDomainRegistration,
2625
- whoisTimeout: args.whoisTimeoutMs ?? 5e3,
2665
+ whoisTimeoutMs: args.whoisTimeoutMs ?? 5e3,
2626
2666
  captureTranscript: args.captureTranscript
2627
2667
  });
2628
2668
  if (args.quiet) {