@emailcheck/email-validator-js 3.4.4-beta.1 → 4.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.js CHANGED
@@ -4,6 +4,7 @@ var tinyLru = require('tiny-lru');
4
4
  var psl = require('psl');
5
5
  var stringSimilarityJs = require('string-similarity-js');
6
6
  var node_dns = require('node:dns');
7
+ var node_crypto = require('node:crypto');
7
8
  var net$1 = require('node:net');
8
9
  var tls = require('node:tls');
9
10
 
@@ -680,9 +681,9 @@ function parseCompositeNamePart(part) {
680
681
  cleaned = pureAlpha;
681
682
  confidence = 0.6;
682
683
  } else {
683
- const baseMatch2 = part.match(/^([a-zA-Z]+)\d*$/);
684
- if (baseMatch2) {
685
- cleaned = baseMatch2[1];
684
+ const baseMatch = part.match(/^([a-zA-Z]+)\d*$/);
685
+ if (baseMatch) {
686
+ cleaned = baseMatch[1];
686
687
  confidence = 0.75;
687
688
  } else {
688
689
  cleaned = part;
@@ -696,9 +697,7 @@ function parseCompositeNamePart(part) {
696
697
  confidence = Math.min(1, confidence + 0.2);
697
698
  }
698
699
  }
699
- const baseMatch = part.match(/^([a-zA-Z]+[a-zA-Z0-9]*?)\d*$/);
700
- const base = baseMatch ? baseMatch[1] : part;
701
- return { base, hasNumbers, cleaned, confidence };
700
+ return { hasNumbers, cleaned, confidence };
702
701
  }
703
702
  function isLikelyName(str, allowNumbers = false, allowSingleLetter = false) {
704
703
  if (!str)
@@ -989,6 +988,9 @@ function parseDsn(reply) {
989
988
  return null;
990
989
  return { class: Number(match[1]), subject: Number(match[2]), detail: Number(match[3]) };
991
990
  }
991
+ function dsnToString(dsn) {
992
+ return `${dsn.class}.${dsn.subject}.${dsn.detail}`;
993
+ }
992
994
  function isPolicyBlock(reply) {
993
995
  const dsn = parseDsn(reply);
994
996
  return (dsn === null || dsn === void 0 ? void 0 : dsn.class) === 5 && (dsn === null || dsn === void 0 ? void 0 : dsn.subject) === 7;
@@ -1009,8 +1011,11 @@ function isInvalidMailboxError(reply) {
1009
1011
  return false;
1010
1012
  return true;
1011
1013
  }
1014
+ function defaultProbeLocal() {
1015
+ return `${node_crypto.randomBytes(8).toString("hex")}-noexist`;
1016
+ }
1012
1017
  async function verifyMailboxSMTP(params) {
1013
- var _a, _b, _c, _d, _e, _f, _g;
1018
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1014
1019
  const { local, domain, options = {} } = params;
1015
1020
  const mxRecords = (_a = params.mxRecords) !== null && _a !== void 0 ? _a : [];
1016
1021
  const ports = ((_b = options.ports) !== null && _b !== void 0 ? _b : DEFAULT_PORTS).filter((port) => Number.isInteger(port) && port > 0 && port < 65536);
@@ -1023,16 +1028,29 @@ async function verifyMailboxSMTP(params) {
1023
1028
  const cache = options.cache;
1024
1029
  const log = debug ? (...args) => console.log("[SMTP]", ...args) : () => {
1025
1030
  };
1026
- const mxHost = mxRecords[0];
1027
- if (!mxHost) {
1031
+ const startedAtMs = Date.now();
1032
+ const primaryMx = mxRecords[0];
1033
+ if (!primaryMx) {
1028
1034
  log("No MX records found");
1029
- return { smtpResult: failureResult("No MX records found"), cached: false, port: 0, portCached: false };
1035
+ const metrics2 = makeMetrics([], 0, 0, void 0, startedAtMs);
1036
+ return { smtpResult: failureResult("no_mx_records", metrics2), cached: false, port: 0, portCached: false };
1030
1037
  }
1031
- log(`Verifying ${local}@${domain} via ${mxHost}`);
1038
+ log(`Verifying ${local}@${domain} via ${primaryMx} (mx count=${mxRecords.length})`);
1032
1039
  const transcript = [];
1033
1040
  const commands = [];
1041
+ const probeOptions = {
1042
+ local,
1043
+ domain,
1044
+ timeout,
1045
+ tlsConfig,
1046
+ hostname,
1047
+ sequence,
1048
+ log,
1049
+ catchAllProbeLocal: options.catchAllProbeLocal,
1050
+ pipelining: (_h = options.pipelining) !== null && _h !== void 0 ? _h : "auto"
1051
+ };
1034
1052
  const verdictCache = cache ? getCacheStore(cache, "smtp") : null;
1035
- const verdictKey = `${mxHost}:${local}@${domain}`;
1053
+ const verdictKey = `${primaryMx}:${local}@${domain}`;
1036
1054
  if (verdictCache) {
1037
1055
  const cachedResult = await safeCacheGet(verdictCache, verdictKey);
1038
1056
  if (cachedResult) {
@@ -1041,81 +1059,99 @@ async function verifyMailboxSMTP(params) {
1041
1059
  }
1042
1060
  }
1043
1061
  const portCache = cache ? getCacheStore(cache, "smtpPort") : null;
1044
- if (portCache) {
1045
- const cachedPort = await safeCacheGet(portCache, mxHost);
1046
- if (cachedPort) {
1047
- log(`Using cached port: ${cachedPort}`);
1048
- const probe = await runProbe({
1049
- mxHost,
1050
- port: cachedPort,
1051
- local,
1052
- domain,
1053
- timeout,
1054
- tlsConfig,
1055
- hostname,
1056
- sequence,
1057
- log
1058
- });
1059
- collectTranscript(transcript, commands, probe, cachedPort);
1060
- const smtpResult = toSmtpVerificationResult(probe.result, captureTranscript ? { transcript, commands } : void 0);
1061
- await safeCacheSet(verdictCache, verdictKey, smtpResult);
1062
- return { smtpResult, cached: false, port: cachedPort, portCached: true };
1063
- }
1064
- }
1065
- for (const port of ports) {
1066
- log(`Testing port ${port}`);
1067
- const probe = await runProbe({ mxHost, port, local, domain, timeout, tlsConfig, hostname, sequence, log });
1068
- collectTranscript(transcript, commands, probe, port);
1069
- const smtpResult = toSmtpVerificationResult(probe.result, captureTranscript ? { transcript, commands } : void 0);
1070
- await safeCacheSet(verdictCache, verdictKey, smtpResult);
1071
- if (probe.result !== null) {
1072
- await safeCacheSet(portCache, mxHost, port);
1073
- return { smtpResult, cached: false, port, portCached: false };
1062
+ const cachedPort = portCache ? await safeCacheGet(portCache, primaryMx) : null;
1063
+ const mxHostsTried = [];
1064
+ let mxAttempts = 0;
1065
+ let portAttempts = 0;
1066
+ let lastReason = "all_attempts_failed";
1067
+ let lastEnhancedStatus;
1068
+ let lastResponseCode;
1069
+ for (const mxHost of mxRecords) {
1070
+ mxHostsTried.push(mxHost);
1071
+ mxAttempts++;
1072
+ const portsForThisMx = mxHost === primaryMx && cachedPort ? [cachedPort, ...ports.filter((p) => p !== cachedPort)] : ports;
1073
+ for (const port of portsForThisMx) {
1074
+ portAttempts++;
1075
+ log(`Testing ${mxHost}:${port}`);
1076
+ const probe = await runProbe({ ...probeOptions, mxHost, port });
1077
+ collectTranscript(transcript, commands, probe, mxHost, port);
1078
+ lastReason = probe.reason;
1079
+ if (probe.enhancedStatus !== void 0)
1080
+ lastEnhancedStatus = probe.enhancedStatus;
1081
+ if (probe.responseCode !== void 0)
1082
+ lastResponseCode = probe.responseCode;
1083
+ if (probe.result !== null) {
1084
+ const metrics2 = makeMetrics(mxHostsTried, mxAttempts, portAttempts, mxHost, startedAtMs);
1085
+ const smtpResult2 = toSmtpVerificationResult(probe, {
1086
+ transcript: captureTranscript ? transcript : void 0,
1087
+ commands: captureTranscript ? commands : void 0,
1088
+ metrics: metrics2
1089
+ });
1090
+ await safeCacheSet(verdictCache, verdictKey, smtpResult2);
1091
+ if (mxHost === primaryMx)
1092
+ await safeCacheSet(portCache, primaryMx, port);
1093
+ return { smtpResult: smtpResult2, cached: false, port, portCached: cachedPort === port };
1094
+ }
1074
1095
  }
1075
1096
  }
1076
- log("All ports failed");
1097
+ log(`All MX\xD7port attempts failed (mx=${mxAttempts}, port=${portAttempts})`);
1098
+ const metrics = makeMetrics(mxHostsTried, mxAttempts, portAttempts, void 0, startedAtMs);
1099
+ const smtpResult = {
1100
+ ...failureResult(lastReason, metrics),
1101
+ ...lastEnhancedStatus !== void 0 ? { enhancedStatus: lastEnhancedStatus } : {},
1102
+ ...lastResponseCode !== void 0 ? { responseCode: lastResponseCode } : {},
1103
+ ...captureTranscript ? { transcript: [...transcript], commands: [...commands] } : {}
1104
+ };
1105
+ return { smtpResult, cached: false, port: 0, portCached: false };
1106
+ }
1107
+ function makeMetrics(mxHostsTried, mxAttempts, portAttempts, mxHostUsed, startedAtMs) {
1077
1108
  return {
1078
- smtpResult: {
1079
- ...failureResult("All SMTP connection attempts failed"),
1080
- ...captureTranscript ? { transcript: [...transcript], commands: [...commands] } : {}
1081
- },
1082
- cached: false,
1083
- port: 0,
1084
- portCached: false
1109
+ mxAttempts,
1110
+ portAttempts,
1111
+ mxHostsTried: [...mxHostsTried],
1112
+ ...mxHostUsed !== void 0 ? { mxHostUsed } : {},
1113
+ totalDurationMs: Date.now() - startedAtMs
1085
1114
  };
1086
1115
  }
1087
- function collectTranscript(transcript, commands, probe, port) {
1116
+ function collectTranscript(transcript, commands, probe, mxHost, port) {
1117
+ const prefix = `${mxHost}:${port}`;
1088
1118
  for (const line of probe.transcript)
1089
- transcript.push(`${port}|s| ${line}`);
1119
+ transcript.push(`${prefix}|s| ${line}`);
1090
1120
  for (const cmd of probe.commands)
1091
- commands.push(`${port}|c| ${cmd}`);
1121
+ commands.push(`${prefix}|c| ${cmd}`);
1092
1122
  }
1093
- function failureResult(error) {
1123
+ function failureResult(reason, metrics) {
1094
1124
  return {
1095
1125
  canConnectSmtp: false,
1096
1126
  hasFullInbox: false,
1097
1127
  isCatchAll: false,
1098
1128
  isDeliverable: false,
1099
1129
  isDisabled: false,
1100
- error,
1101
- providerUsed: exports.EmailProvider.everythingElse,
1102
- checkedAt: Date.now()
1130
+ error: reason,
1131
+ checkedAt: Date.now(),
1132
+ metrics
1103
1133
  };
1104
1134
  }
1105
- function toSmtpVerificationResult(result, capture) {
1106
- const base = {
1135
+ function toSmtpVerificationResult(probe, extras) {
1136
+ var _a;
1137
+ const result = probe.result;
1138
+ const out = {
1107
1139
  canConnectSmtp: result !== null,
1108
- hasFullInbox: false,
1109
- isCatchAll: false,
1140
+ hasFullInbox: probe.reason === "over_quota",
1141
+ isCatchAll: (_a = probe.isCatchAll) !== null && _a !== void 0 ? _a : false,
1110
1142
  isDeliverable: result === true,
1111
1143
  isDisabled: result === false,
1112
- error: result === true ? void 0 : result === null ? "ambiguous" : "not_found",
1113
- providerUsed: exports.EmailProvider.everythingElse,
1114
- checkedAt: Date.now()
1144
+ error: result === true ? void 0 : probe.reason,
1145
+ checkedAt: Date.now(),
1146
+ metrics: extras.metrics,
1147
+ ...probe.enhancedStatus !== void 0 ? { enhancedStatus: probe.enhancedStatus } : {},
1148
+ ...probe.responseCode !== void 0 ? { responseCode: probe.responseCode } : {}
1115
1149
  };
1116
- if (!capture)
1117
- return base;
1118
- return { ...base, transcript: [...capture.transcript], commands: [...capture.commands] };
1150
+ if (extras.transcript)
1151
+ out.transcript = [...extras.transcript];
1152
+ if (extras.commands)
1153
+ out.commands = [...extras.commands];
1154
+ return out;
1119
1155
  }
1120
1156
  async function safeCacheGet(store, key) {
1121
1157
  if (!store)
@@ -1147,6 +1183,12 @@ class SMTPProbeConnection {
1147
1183
  this.currentStepIndex = 0;
1148
1184
  this.transcript = [];
1149
1185
  this.commands = [];
1186
+ this.supportsPipelining = false;
1187
+ this.dualPhase = "idle";
1188
+ this.realOutcome = "pending";
1189
+ this.probeOutcome = "pending";
1190
+ this.dualPipelined = false;
1191
+ this.pendingDecision = null;
1150
1192
  this.onData = (data) => {
1151
1193
  if (this.resolved)
1152
1194
  return;
@@ -1170,6 +1212,7 @@ class SMTPProbeConnection {
1170
1212
  minVersion: "TLSv1.2",
1171
1213
  ...typeof p.tlsConfig === "object" ? p.tlsConfig : {}
1172
1214
  };
1215
+ this.probeLocal = p.catchAllProbeLocal ? p.catchAllProbeLocal(p.local, p.domain) : defaultProbeLocal();
1173
1216
  }
1174
1217
  run() {
1175
1218
  return new Promise((resolve) => {
@@ -1230,6 +1273,7 @@ class SMTPProbeConnection {
1230
1273
  switch (step) {
1231
1274
  case exports.SMTPStep.greeting:
1232
1275
  return;
1276
+ // server-driven; nothing to send
1233
1277
  case exports.SMTPStep.ehlo:
1234
1278
  this.send(`EHLO ${this.p.hostname}`);
1235
1279
  return;
@@ -1242,39 +1286,87 @@ class SMTPProbeConnection {
1242
1286
  return;
1243
1287
  }
1244
1288
  case exports.SMTPStep.rcptTo:
1245
- this.send(`RCPT TO:<${this.p.local}@${this.p.domain}>`);
1289
+ this.executeEnvelope();
1246
1290
  return;
1247
1291
  }
1248
1292
  }
1293
+ /**
1294
+ * Send the dual-probe envelope (real RCPT + probe RCPT + RSET). Pipelined
1295
+ * when the MX advertised PIPELINING (or `pipelining: 'force'`); sequential
1296
+ * otherwise.
1297
+ */
1298
+ executeEnvelope() {
1299
+ var _a;
1300
+ const wantsPipelining = this.p.pipelining === "force" || this.p.pipelining === "auto" && this.supportsPipelining;
1301
+ const realCmd = `RCPT TO:<${this.p.local}@${this.p.domain}>`;
1302
+ if (wantsPipelining) {
1303
+ this.dualPipelined = true;
1304
+ const probeCmd = `RCPT TO:<${this.probeLocal}@${this.p.domain}>`;
1305
+ const rsetCmd = "RSET";
1306
+ this.commands.push(realCmd, probeCmd, rsetCmd);
1307
+ this.p.log(`\u2192 ${realCmd}`);
1308
+ this.p.log(`\u2192 ${probeCmd}`);
1309
+ this.p.log(`\u2192 ${rsetCmd}`);
1310
+ (_a = this.socket) === null || _a === void 0 ? void 0 : _a.write(`${realCmd}\r
1311
+ ${probeCmd}\r
1312
+ ${rsetCmd}\r
1313
+ `);
1314
+ this.dualPhase = "rcpt_real";
1315
+ return;
1316
+ }
1317
+ this.dualPipelined = false;
1318
+ this.send(realCmd);
1319
+ this.dualPhase = "rcpt_real";
1320
+ }
1249
1321
  processLine(line) {
1250
1322
  if (this.resolved)
1251
1323
  return;
1252
1324
  this.transcript.push(line);
1253
1325
  this.p.log(`\u2190 ${line}`);
1254
- if (isHighVolume(line)) {
1255
- this.finish(true, "high_volume");
1256
- return;
1257
- }
1258
- if (isOverQuota(line)) {
1259
- this.finish(false, "over_quota");
1260
- return;
1326
+ const codeStr = line.slice(0, 3);
1327
+ const numericCode = /^\d{3}$/.test(codeStr) ? parseInt(codeStr, 10) : null;
1328
+ if (numericCode !== null)
1329
+ this.lastResponseCode = numericCode;
1330
+ const dsn = parseDsn(line);
1331
+ if (dsn)
1332
+ this.lastEnhancedStatus = dsnToString(dsn);
1333
+ if (this.dualPhase === "idle" || this.dualPhase === "rcpt_real") {
1334
+ if (isHighVolume(line)) {
1335
+ this.finish(true, "high_volume");
1336
+ return;
1337
+ }
1338
+ if (isOverQuota(line)) {
1339
+ this.isCatchAllFlag = false;
1340
+ this.finish(false, "over_quota");
1341
+ return;
1342
+ }
1343
+ if (isInvalidMailboxError(line)) {
1344
+ this.isCatchAllFlag = false;
1345
+ this.finish(false, "not_found");
1346
+ return;
1347
+ }
1261
1348
  }
1262
- if (isInvalidMailboxError(line)) {
1263
- this.finish(false, "not_found");
1349
+ if (MULTILINE_RE.test(line)) {
1350
+ const step = this.steps[this.currentStepIndex];
1351
+ if ((step === exports.SMTPStep.ehlo || step === exports.SMTPStep.helo) && line.startsWith("250-")) {
1352
+ const upper = line.toUpperCase();
1353
+ if (upper.includes("PIPELINING"))
1354
+ this.supportsPipelining = true;
1355
+ }
1264
1356
  return;
1265
1357
  }
1266
- if (MULTILINE_RE.test(line))
1267
- return;
1268
- const code = line.slice(0, 3);
1269
- const numericCode = /^\d{3}$/.test(code) ? parseInt(code, 10) : null;
1270
1358
  if (numericCode === null) {
1271
1359
  this.finish(null, "unrecognized_response");
1272
1360
  return;
1273
1361
  }
1274
- this.dispatch(numericCode);
1362
+ this.dispatch(numericCode, line);
1275
1363
  }
1276
- dispatch(code) {
1364
+ dispatch(code, line) {
1277
1365
  const step = this.steps[this.currentStepIndex];
1366
+ if (this.dualPhase !== "idle" && step === exports.SMTPStep.rcptTo) {
1367
+ this.handleEnvelopeReply(code, line);
1368
+ return;
1369
+ }
1278
1370
  switch (step) {
1279
1371
  case exports.SMTPStep.greeting:
1280
1372
  if (code === 220)
@@ -1301,17 +1393,93 @@ class SMTPProbeConnection {
1301
1393
  this.finish(null, "mail_from_rejected");
1302
1394
  return;
1303
1395
  case exports.SMTPStep.rcptTo:
1304
- if (code === 250 || code === 251)
1305
- this.finish(true, "valid");
1306
- else if (code === 552 || code === 452)
1307
- this.finish(false, "over_quota");
1308
- else if (code >= 400 && code < 500)
1309
- this.finish(null, "temporary_failure");
1310
- else
1311
- this.finish(null, "ambiguous");
1396
+ this.handleEnvelopeReply(code, line);
1312
1397
  return;
1313
1398
  }
1314
1399
  }
1400
+ /**
1401
+ * Dual-probe / pipelined-envelope reply router. Demuxes server replies for
1402
+ * the three queued commands (real RCPT, probe RCPT, RSET) and resolves
1403
+ * with the catch-all-aware verdict.
1404
+ */
1405
+ handleEnvelopeReply(code, line) {
1406
+ if (this.dualPhase === "rcpt_real") {
1407
+ this.realOutcome = classifyRcpt(code);
1408
+ if (code === 552 || code === 452 || isOverQuota(line)) {
1409
+ this.isCatchAllFlag = false;
1410
+ if (this.dualPipelined) {
1411
+ this.pendingDecision = { result: false, reason: "over_quota" };
1412
+ this.dualPhase = "rcpt_probe";
1413
+ return;
1414
+ }
1415
+ this.finish(false, "over_quota");
1416
+ return;
1417
+ }
1418
+ if (this.realOutcome === "soft_reject") {
1419
+ if (this.dualPipelined) {
1420
+ this.pendingDecision = { result: null, reason: "temporary_failure" };
1421
+ this.dualPhase = "rcpt_probe";
1422
+ return;
1423
+ }
1424
+ this.finish(null, "temporary_failure");
1425
+ return;
1426
+ }
1427
+ if (this.realOutcome === "hard_reject") {
1428
+ const reason = isInvalidMailboxError(line) ? "not_found" : "ambiguous";
1429
+ const result = reason === "not_found" ? false : null;
1430
+ this.isCatchAllFlag = false;
1431
+ if (this.dualPipelined) {
1432
+ this.pendingDecision = { result, reason };
1433
+ this.dualPhase = "rcpt_probe";
1434
+ return;
1435
+ }
1436
+ this.finish(result, reason);
1437
+ return;
1438
+ }
1439
+ if (this.dualPipelined) {
1440
+ this.dualPhase = "rcpt_probe";
1441
+ } else {
1442
+ this.send(`RCPT TO:<${this.probeLocal}@${this.p.domain}>`);
1443
+ this.dualPhase = "rcpt_probe";
1444
+ }
1445
+ return;
1446
+ }
1447
+ if (this.dualPhase === "rcpt_probe") {
1448
+ this.probeOutcome = classifyRcpt(code);
1449
+ if (this.dualPipelined) {
1450
+ this.dualPhase = "rset";
1451
+ } else {
1452
+ this.send("RSET");
1453
+ this.dualPhase = "rset";
1454
+ }
1455
+ return;
1456
+ }
1457
+ if (this.dualPhase === "rset") {
1458
+ if (this.pendingDecision) {
1459
+ this.finish(this.pendingDecision.result, this.pendingDecision.reason);
1460
+ return;
1461
+ }
1462
+ this.decideDualProbe();
1463
+ return;
1464
+ }
1465
+ }
1466
+ /** Final decision after both RCPT outcomes are known. Catch-all only when both 250. */
1467
+ decideDualProbe() {
1468
+ if (this.realOutcome === "accept" && this.probeOutcome === "accept") {
1469
+ this.isCatchAllFlag = true;
1470
+ this.finish(true, "valid");
1471
+ } else if (this.realOutcome === "accept") {
1472
+ this.isCatchAllFlag = false;
1473
+ this.finish(true, "valid");
1474
+ } else if (this.realOutcome === "hard_reject") {
1475
+ this.isCatchAllFlag = false;
1476
+ this.finish(false, "not_found");
1477
+ } else if (this.realOutcome === "soft_reject") {
1478
+ this.finish(null, "temporary_failure");
1479
+ } else {
1480
+ this.finish(null, "ambiguous");
1481
+ }
1482
+ }
1315
1483
  finish(result, reason) {
1316
1484
  var _a, _b, _c;
1317
1485
  if (this.resolved)
@@ -1335,9 +1503,24 @@ class SMTPProbeConnection {
1335
1503
  return (_a2 = this.socket) === null || _a2 === void 0 ? void 0 : _a2.destroy();
1336
1504
  }, QUIT_DRAIN_MS);
1337
1505
  (_c = drain.unref) === null || _c === void 0 ? void 0 : _c.call(drain);
1338
- this.resolveFn({ result, transcript: this.transcript, commands: this.commands });
1506
+ this.resolveFn({
1507
+ result,
1508
+ reason,
1509
+ ...this.lastEnhancedStatus !== void 0 ? { enhancedStatus: this.lastEnhancedStatus } : {},
1510
+ ...this.lastResponseCode !== void 0 ? { responseCode: this.lastResponseCode } : {},
1511
+ ...this.isCatchAllFlag !== void 0 ? { isCatchAll: this.isCatchAllFlag } : {},
1512
+ transcript: this.transcript,
1513
+ commands: this.commands
1514
+ });
1339
1515
  }
1340
1516
  }
1517
+ function classifyRcpt(code) {
1518
+ if (code === 250 || code === 251)
1519
+ return "accept";
1520
+ if (code >= 400 && code < 500)
1521
+ return "soft_reject";
1522
+ return "hard_reject";
1523
+ }
1341
1524
 
1342
1525
  class ArrayTranscriptCollector {
1343
1526
  constructor() {
@@ -2010,12 +2193,11 @@ async function verifyEmail(params) {
2010
2193
  const skipMx = ((_f = params.skipMxForDisposable) !== null && _f !== void 0 ? _f : false) && result.isDisposable;
2011
2194
  const skipWhois = ((_g = params.skipDomainWhoisForDisposable) !== null && _g !== void 0 ? _g : false) && result.isDisposable;
2012
2195
  await runWhoisChecks(domain, params, result, skipWhois, log, collector);
2013
- if (((_h = params.verifyMx) !== null && _h !== void 0 ? _h : true) || ((_j = params.verifySmtp) !== null && _j !== void 0 ? _j : false)) {
2014
- if (skipMx) {
2015
- log(`[verifyEmail] skipping MX/SMTP for disposable: ${params.emailAddress}`);
2016
- } else {
2017
- await runMxAndSmtp(local, domain, params, result, log, collector);
2018
- }
2196
+ const wantsMxOrSmtp = ((_h = params.verifyMx) !== null && _h !== void 0 ? _h : true) || ((_j = params.verifySmtp) !== null && _j !== void 0 ? _j : false);
2197
+ if (wantsMxOrSmtp && skipMx) {
2198
+ log(`[verifyEmail] skipping MX/SMTP for disposable: ${params.emailAddress}`);
2199
+ } else if (wantsMxOrSmtp) {
2200
+ await runMxAndSmtp(local, domain, params, result, log, collector);
2019
2201
  }
2020
2202
  result.metadata.verificationTime = Date.now() - startTime;
2021
2203
  if (captureTranscript)