@emailcheck/email-validator-js 4.0.0 → 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/index.d.ts CHANGED
@@ -5,22 +5,29 @@
5
5
  * (`isDisposableEmail`, `isFreeEmail`) live in their own modules so
6
6
  * `batch-verifier.ts` can pull `verifyEmail` directly without going through
7
7
  * this file. That broke a `index → batch-verifier → index` Rollup cycle.
8
+ *
9
+ * Anything `export`ed under `src/*.ts` should also be re-exported here so
10
+ * `import { … } from '@emailcheck/email-validator-js'` works for every
11
+ * public symbol. See the audit comment per-line below.
8
12
  */
9
13
  export * from './adapters/lru-adapter';
10
14
  export * from './adapters/redis-adapter';
11
15
  export { verifyEmailBatch } from './batch-verifier';
12
16
  export * from './cache';
13
17
  export * from './cache-interface';
14
- export { commonEmailDomains, defaultDomainSuggestionMethod, getDomainSimilarity, isCommonDomain, suggestDomain, suggestEmailDomain, } from './domain-suggester';
18
+ export { commonEmailDomains, defaultDomainSuggestionMethod, defaultDomainSuggestionMethodAsync, getDomainSimilarity, isCommonDomain, suggestDomain, suggestEmailDomain, } from './domain-suggester';
15
19
  export { isValidEmail, isValidEmailDomain } from './email-validator';
16
20
  export { isDisposableEmail } from './is-disposable-email';
17
21
  export { isFreeEmail } from './is-free-email';
18
22
  export { isSpamEmail } from './is-spam-email';
19
23
  export { isSpamName } from './is-spam-name';
24
+ export { resolveMxRecords } from './mx-resolver';
20
25
  export { cleanNameForAlgorithm, defaultNameDetectionMethod, detectName, detectNameForAlgorithm, detectNameFromEmail, } from './name-detector';
21
26
  export { refineReasonByEnhancedStatus } from './refine-reason';
22
27
  export { type ParsedSmtpError, parseSmtpError } from './smtp-error-parser';
28
+ export { type ParsedDsn, parseDsn, verifyMailboxSMTP } from './smtp-verifier';
23
29
  export { ArrayTranscriptCollector, NULL_COLLECTOR, type TranscriptCollector, } from './transcript';
24
30
  export * from './types';
25
31
  export { domainPorts, verifyEmail } from './verify-email';
26
32
  export { getDomainAge, getDomainRegistrationStatus } from './whois';
33
+ export { type ParsedWhoisResult, parseWhoisData } from './whois-parser';
package/dist/index.esm.js CHANGED
@@ -992,19 +992,25 @@ function defaultProbeLocal() {
992
992
  return `${randomBytes(8).toString("hex")}-noexist`;
993
993
  }
994
994
  async function verifyMailboxSMTP(params) {
995
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
995
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
996
996
  const { local, domain, options = {} } = params;
997
997
  const mxRecords = (_a = params.mxRecords) !== null && _a !== void 0 ? _a : [];
998
998
  const ports = ((_b = options.ports) !== null && _b !== void 0 ? _b : DEFAULT_PORTS).filter((port) => Number.isInteger(port) && port > 0 && port < 65536);
999
- const timeout = (_c = options.timeout) !== null && _c !== void 0 ? _c : DEFAULT_TIMEOUT_MS;
1000
- const tlsConfig = (_d = options.tls) !== null && _d !== void 0 ? _d : true;
1001
- const hostname = (_e = options.hostname) !== null && _e !== void 0 ? _e : "localhost";
999
+ const perAttemptTimeoutMs = (_c = options.perAttemptTimeoutMs) !== null && _c !== void 0 ? _c : DEFAULT_TIMEOUT_MS;
1000
+ const tlsConfig = (_d = options.tlsConfig) !== null && _d !== void 0 ? _d : true;
1001
+ const heloHostname = (_e = options.heloHostname) !== null && _e !== void 0 ? _e : "localhost";
1002
1002
  const debug = (_f = options.debug) !== null && _f !== void 0 ? _f : false;
1003
1003
  const captureTranscript = (_g = options.captureTranscript) !== null && _g !== void 0 ? _g : false;
1004
1004
  const sequence = options.sequence;
1005
1005
  const cache = options.cache;
1006
1006
  const log = debug ? (...args) => console.log("[SMTP]", ...args) : () => {
1007
1007
  };
1008
+ const totalDeadlineMs = options.totalDeadlineMs;
1009
+ const maxConsecutiveFailures = options.maxConsecutiveFailures;
1010
+ const maxMxHosts = options.maxMxHosts;
1011
+ const retryAttempts = (_j = (_h = options.retry) === null || _h === void 0 ? void 0 : _h.attempts) !== null && _j !== void 0 ? _j : 0;
1012
+ const retryDelayMs = (_l = (_k = options.retry) === null || _k === void 0 ? void 0 : _k.delayMs) !== null && _l !== void 0 ? _l : 200;
1013
+ const retryBackoff = (_o = (_m = options.retry) === null || _m === void 0 ? void 0 : _m.backoff) !== null && _o !== void 0 ? _o : "exponential";
1008
1014
  const startedAtMs = Date.now();
1009
1015
  const primaryMx = mxRecords[0];
1010
1016
  if (!primaryMx) {
@@ -1018,14 +1024,14 @@ async function verifyMailboxSMTP(params) {
1018
1024
  const probeOptions = {
1019
1025
  local,
1020
1026
  domain,
1021
- timeout,
1027
+ perAttemptTimeoutMs,
1022
1028
  tlsConfig,
1023
- hostname,
1029
+ heloHostname,
1024
1030
  sequence,
1025
1031
  log,
1026
1032
  catchAllProbeLocal: options.catchAllProbeLocal,
1027
- pipelining: (_h = options.pipelining) !== null && _h !== void 0 ? _h : "auto",
1028
- startTls: (_j = options.startTls) !== null && _j !== void 0 ? _j : "auto"
1033
+ pipelining: (_p = options.pipelining) !== null && _p !== void 0 ? _p : "auto",
1034
+ startTls: (_q = options.startTls) !== null && _q !== void 0 ? _q : "auto"
1029
1035
  };
1030
1036
  const verdictCache = cache ? getCacheStore(cache, "smtp") : null;
1031
1037
  const verdictKey = `${primaryMx}:${local}@${domain}`;
@@ -1041,17 +1047,42 @@ async function verifyMailboxSMTP(params) {
1041
1047
  const mxHostsTried = [];
1042
1048
  let mxAttempts = 0;
1043
1049
  let portAttempts = 0;
1050
+ let consecutiveFailures = 0;
1044
1051
  let lastReason = "all_attempts_failed";
1045
1052
  let lastEnhancedStatus;
1046
1053
  let lastResponseCode;
1047
- for (const mxHost of mxRecords) {
1054
+ let stoppedEarly = null;
1055
+ const isConnectionFailure = (reason) => reason === "connection_error" || reason === "connection_timeout" || reason === "connection_closed" || reason === "socket_timeout";
1056
+ const retryDelayFor = (attemptIndex) => retryBackoff === "exponential" ? retryDelayMs * 2 ** (attemptIndex - 1) : retryDelayMs;
1057
+ const probeWithRetry = async (mxHost, port) => {
1058
+ let lastProbe = await runProbe({ ...probeOptions, mxHost, port });
1059
+ for (let i = 1; i <= retryAttempts; i++) {
1060
+ if (lastProbe.result !== null || !isConnectionFailure(lastProbe.reason))
1061
+ break;
1062
+ const delay = retryDelayFor(i);
1063
+ log(`retry ${i}/${retryAttempts} on ${mxHost}:${port} after ${delay}ms (last: ${lastProbe.reason})`);
1064
+ await new Promise((r) => setTimeout(r, delay));
1065
+ portAttempts++;
1066
+ lastProbe = await runProbe({ ...probeOptions, mxHost, port });
1067
+ }
1068
+ return lastProbe;
1069
+ };
1070
+ outer: for (const mxHost of mxRecords) {
1071
+ if (maxMxHosts !== void 0 && mxAttempts >= maxMxHosts) {
1072
+ stoppedEarly = "max_mx_hosts";
1073
+ break;
1074
+ }
1048
1075
  mxHostsTried.push(mxHost);
1049
1076
  mxAttempts++;
1050
1077
  const portsForThisMx = mxHost === primaryMx && cachedPort ? [cachedPort, ...ports.filter((p) => p !== cachedPort)] : ports;
1051
1078
  for (const port of portsForThisMx) {
1079
+ if (totalDeadlineMs !== void 0 && Date.now() - startedAtMs >= totalDeadlineMs) {
1080
+ stoppedEarly = "deadline";
1081
+ break outer;
1082
+ }
1052
1083
  portAttempts++;
1053
1084
  log(`Testing ${mxHost}:${port}`);
1054
- const probe = await runProbe({ ...probeOptions, mxHost, port });
1085
+ const probe = await probeWithRetry(mxHost, port);
1055
1086
  collectTranscript(transcript, commands, probe, mxHost, port);
1056
1087
  lastReason = probe.reason;
1057
1088
  if (probe.enhancedStatus !== void 0)
@@ -1070,8 +1101,15 @@ async function verifyMailboxSMTP(params) {
1070
1101
  await safeCacheSet(portCache, primaryMx, port);
1071
1102
  return { smtpResult: smtpResult2, cached: false, port, portCached: cachedPort === port };
1072
1103
  }
1104
+ consecutiveFailures = isConnectionFailure(probe.reason) ? consecutiveFailures + 1 : 0;
1105
+ if (maxConsecutiveFailures !== void 0 && consecutiveFailures >= maxConsecutiveFailures) {
1106
+ stoppedEarly = "consecutive_failures";
1107
+ break outer;
1108
+ }
1073
1109
  }
1074
1110
  }
1111
+ if (stoppedEarly)
1112
+ log(`Stopped early: ${stoppedEarly}`);
1075
1113
  log(`All MX\xD7port attempts failed (mx=${mxAttempts}, port=${portAttempts})`);
1076
1114
  const metrics = makeMetrics(mxHostsTried, mxAttempts, portAttempts, void 0, startedAtMs);
1077
1115
  const smtpResult = {
@@ -1218,17 +1256,17 @@ class SMTPProbeConnection {
1218
1256
  } else {
1219
1257
  this.socket = net$1.connect({ host: this.p.mxHost, port: this.p.port }, onConnect);
1220
1258
  }
1221
- this.socket.setTimeout(this.p.timeout, () => this.finish(null, "socket_timeout"));
1259
+ this.socket.setTimeout(this.p.perAttemptTimeoutMs, () => this.finish(null, "socket_timeout"));
1222
1260
  this.socket.on("error", () => this.finish(null, "connection_error"));
1223
1261
  this.socket.on("close", () => this.finish(null, "connection_closed"));
1224
1262
  }
1225
1263
  armConnectionTimer() {
1226
- this.connectionTimer = setTimeout(() => this.finish(null, "connection_timeout"), this.p.timeout);
1264
+ this.connectionTimer = setTimeout(() => this.finish(null, "connection_timeout"), this.p.perAttemptTimeoutMs);
1227
1265
  }
1228
1266
  resetStepTimer() {
1229
1267
  if (this.stepTimer)
1230
1268
  clearTimeout(this.stepTimer);
1231
- this.stepTimer = setTimeout(() => this.finish(null, "step_timeout"), this.p.timeout);
1269
+ this.stepTimer = setTimeout(() => this.finish(null, "step_timeout"), this.p.perAttemptTimeoutMs);
1232
1270
  }
1233
1271
  send(cmd) {
1234
1272
  var _a;
@@ -1257,10 +1295,10 @@ class SMTPProbeConnection {
1257
1295
  return;
1258
1296
  // server-driven; nothing to send
1259
1297
  case SMTPStep.ehlo:
1260
- this.send(`EHLO ${this.p.hostname}`);
1298
+ this.send(`EHLO ${this.p.heloHostname}`);
1261
1299
  return;
1262
1300
  case SMTPStep.helo:
1263
- this.send(`HELO ${this.p.hostname}`);
1301
+ this.send(`HELO ${this.p.heloHostname}`);
1264
1302
  return;
1265
1303
  case SMTPStep.startTls:
1266
1304
  this.executeStartTls();
@@ -1332,7 +1370,7 @@ class SMTPProbeConnection {
1332
1370
  this.supportsStartTls = false;
1333
1371
  this.supportsPipelining = false;
1334
1372
  this.postUpgradeReEhlo = true;
1335
- this.send(`EHLO ${this.p.hostname}`);
1373
+ this.send(`EHLO ${this.p.heloHostname}`);
1336
1374
  });
1337
1375
  this.socket = tlsSocket;
1338
1376
  this.socket.on("data", this.onData);
@@ -1340,7 +1378,7 @@ class SMTPProbeConnection {
1340
1378
  this.finish(null, this.tlsUpgrading ? "tls_handshake_failed" : "connection_error");
1341
1379
  });
1342
1380
  this.socket.on("close", () => this.finish(null, "connection_closed"));
1343
- this.socket.setTimeout(this.p.timeout, () => this.finish(null, "socket_timeout"));
1381
+ this.socket.setTimeout(this.p.perAttemptTimeoutMs, () => this.finish(null, "socket_timeout"));
1344
1382
  }
1345
1383
  /**
1346
1384
  * Send the dual-probe envelope (real RCPT + probe RCPT + RSET). Pipelined
@@ -2309,11 +2347,11 @@ async function runWhoisChecks(domain, params, result, skipWhois, log, collector)
2309
2347
  log(`[verifyEmail] WHOIS checks skipped for disposable: ${domain}`);
2310
2348
  return;
2311
2349
  }
2312
- const whoisTimeout = (_a = params.whoisTimeout) !== null && _a !== void 0 ? _a : 5e3;
2350
+ const whoisTimeoutMs = (_a = params.whoisTimeoutMs) !== null && _a !== void 0 ? _a : 5e3;
2313
2351
  const debug = (_b = params.debug) !== null && _b !== void 0 ? _b : false;
2314
2352
  if (params.checkDomainAge) {
2315
2353
  try {
2316
- result.domainAge = await collector.record("whois-age", () => getDomainAge(domain, whoisTimeout, debug, params.cache), (info) => {
2354
+ result.domainAge = await collector.record("whois-age", () => getDomainAge(domain, whoisTimeoutMs, debug, params.cache), (info) => {
2317
2355
  var _a2, _b2, _c2, _d2, _e, _f;
2318
2356
  return {
2319
2357
  domain,
@@ -2332,7 +2370,7 @@ async function runWhoisChecks(domain, params, result, skipWhois, log, collector)
2332
2370
  }
2333
2371
  if (params.checkDomainRegistration) {
2334
2372
  try {
2335
- result.domainRegistration = await collector.record("whois-registration", () => getDomainRegistrationStatus(domain, whoisTimeout, debug, params.cache), (info) => {
2373
+ result.domainRegistration = await collector.record("whois-registration", () => getDomainRegistrationStatus(domain, whoisTimeoutMs, debug, params.cache), (info) => {
2336
2374
  var _a2, _b2, _c2, _d2, _e, _f;
2337
2375
  return {
2338
2376
  domain,
@@ -2403,7 +2441,11 @@ async function runSmtp(local, domain, mxRecords, params, result, log, collector)
2403
2441
  options: {
2404
2442
  cache: params.cache,
2405
2443
  ports: resolveSmtpPorts(params.smtpPort, mxRecords[0]),
2406
- timeout: (_a = params.timeout) !== null && _a !== void 0 ? _a : 4e3,
2444
+ perAttemptTimeoutMs: (_a = params.smtpPerAttemptTimeoutMs) !== null && _a !== void 0 ? _a : 4e3,
2445
+ totalDeadlineMs: params.smtpTotalDeadlineMs,
2446
+ maxConsecutiveFailures: params.smtpMaxConsecutiveFailures,
2447
+ maxMxHosts: params.smtpMaxMxHosts,
2448
+ retry: params.smtpRetry,
2407
2449
  debug: (_b = params.debug) !== null && _b !== void 0 ? _b : false,
2408
2450
  // Forward transcript capture so the SMTP step's details include
2409
2451
  // the full per-port transcript when the caller asked for it.
@@ -2444,7 +2486,7 @@ function smtpVerdictFor(validSmtp) {
2444
2486
  }
2445
2487
 
2446
2488
  async function verifyEmailBatch(params) {
2447
- const { emailAddresses, concurrency = 5, timeout = 4e3, verifyMx = true, verifySmtp = false, checkDisposable = true, checkFree = true, detectName = false, nameDetectionMethod, suggestDomain = false, domainSuggestionMethod, commonDomains, skipMxForDisposable = false, skipDomainWhoisForDisposable = false, cache } = params;
2489
+ const { emailAddresses, concurrency = 5, smtpPerAttemptTimeoutMs = 4e3, smtpTotalDeadlineMs, smtpMaxConsecutiveFailures, smtpMaxMxHosts, smtpRetry, verifyMx = true, verifySmtp = false, checkDisposable = true, checkFree = true, detectName = false, nameDetectionMethod, suggestDomain = false, domainSuggestionMethod, commonDomains, skipMxForDisposable = false, skipDomainWhoisForDisposable = false, cache } = params;
2448
2490
  const startTime = Date.now();
2449
2491
  const results = /* @__PURE__ */ new Map();
2450
2492
  const batches = [];
@@ -2459,7 +2501,11 @@ async function verifyEmailBatch(params) {
2459
2501
  try {
2460
2502
  const result = await verifyEmail({
2461
2503
  emailAddress: email,
2462
- timeout,
2504
+ smtpPerAttemptTimeoutMs,
2505
+ smtpTotalDeadlineMs,
2506
+ smtpMaxConsecutiveFailures,
2507
+ smtpMaxMxHosts,
2508
+ smtpRetry,
2463
2509
  verifyMx,
2464
2510
  verifySmtp,
2465
2511
  checkDisposable,
@@ -2750,5 +2796,5 @@ function parseSmtpError(errorMessage) {
2750
2796
  return { isDisabled, hasFullInbox, isCatchAll, isInvalid };
2751
2797
  }
2752
2798
 
2753
- export { ArrayTranscriptCollector, DEFAULT_CACHE_OPTIONS, EmailProvider, LRUAdapter, NULL_COLLECTOR, RedisAdapter, SMTPStep, VerificationErrorCode, cleanNameForAlgorithm, clearDefaultCache, commonEmailDomains, defaultDomainSuggestionMethod, defaultNameDetectionMethod, detectName, detectNameForAlgorithm, detectNameFromEmail, domainPorts, getCacheStore, getDefaultCache, getDomainAge, getDomainRegistrationStatus, getDomainSimilarity, isCommonDomain, isDisposableEmail, isFreeEmail, isSpamEmail, isSpamName, isValidEmail, isValidEmailDomain, parseSmtpError, refineReasonByEnhancedStatus, suggestDomain, suggestEmailDomain, verifyEmail, verifyEmailBatch };
2799
+ export { ArrayTranscriptCollector, DEFAULT_CACHE_OPTIONS, EmailProvider, LRUAdapter, NULL_COLLECTOR, RedisAdapter, SMTPStep, VerificationErrorCode, cleanNameForAlgorithm, clearDefaultCache, commonEmailDomains, defaultDomainSuggestionMethod, defaultDomainSuggestionMethodAsync, defaultNameDetectionMethod, detectName, detectNameForAlgorithm, detectNameFromEmail, domainPorts, getCacheStore, getDefaultCache, getDomainAge, getDomainRegistrationStatus, getDomainSimilarity, isCommonDomain, isDisposableEmail, isFreeEmail, isSpamEmail, isSpamName, isValidEmail, isValidEmailDomain, parseDsn, parseSmtpError, parseWhoisData, refineReasonByEnhancedStatus, resolveMxRecords, suggestDomain, suggestEmailDomain, verifyEmail, verifyEmailBatch, verifyMailboxSMTP };
2754
2800
  //# sourceMappingURL=index.esm.js.map