@emailcheck/email-validator-js 2.14.1 → 3.0.1-beta.0

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
@@ -5,6 +5,7 @@ var tinyLru = require('tiny-lru');
5
5
  var node_dns = require('node:dns');
6
6
  var stringSimilarityJs = require('string-similarity-js');
7
7
  var net = require('node:net');
8
+ var tls = require('node:tls');
8
9
 
9
10
  function _interopNamespaceDefault(e) {
10
11
  var n = Object.create(null);
@@ -24,6 +25,7 @@ function _interopNamespaceDefault(e) {
24
25
  }
25
26
 
26
27
  var net__namespace = /*#__PURE__*/_interopNamespaceDefault(net);
28
+ var tls__namespace = /*#__PURE__*/_interopNamespaceDefault(tls);
27
29
 
28
30
  class LRUAdapter {
29
31
  constructor(maxSize = 1e3, ttlMs = 36e5) {
@@ -73,6 +75,8 @@ const DEFAULT_CACHE_OPTIONS = {
73
75
  // 24 hours
74
76
  smtp: 18e5,
75
77
  // 30 minutes
78
+ smtpPort: 36e5,
79
+ // 1 hour
76
80
  domainSuggestion: 864e5,
77
81
  // 24 hours
78
82
  whois: 36e5
@@ -84,6 +88,7 @@ const DEFAULT_CACHE_OPTIONS = {
84
88
  free: 1e3,
85
89
  domainValid: 1e3,
86
90
  smtp: 500,
91
+ smtpPort: 500,
87
92
  domainSuggestion: 1e3,
88
93
  whois: 200
89
94
  }
@@ -97,6 +102,7 @@ function getDefaultCache() {
97
102
  free: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.free, DEFAULT_CACHE_OPTIONS.ttl.free),
98
103
  domainValid: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.domainValid, DEFAULT_CACHE_OPTIONS.ttl.domainValid),
99
104
  smtp: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.smtp, DEFAULT_CACHE_OPTIONS.ttl.smtp),
105
+ smtpPort: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.smtpPort, DEFAULT_CACHE_OPTIONS.ttl.smtpPort),
100
106
  domainSuggestion: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.domainSuggestion, DEFAULT_CACHE_OPTIONS.ttl.domainSuggestion),
101
107
  whois: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.whois, DEFAULT_CACHE_OPTIONS.ttl.whois)
102
108
  };
@@ -113,6 +119,7 @@ function clearDefaultCache() {
113
119
  defaultCacheInstance.free.clear();
114
120
  defaultCacheInstance.domainValid.clear();
115
121
  defaultCacheInstance.smtp.clear();
122
+ defaultCacheInstance.smtpPort.clear();
116
123
  defaultCacheInstance.domainSuggestion.clear();
117
124
  defaultCacheInstance.whois.clear();
118
125
  }
@@ -1147,6 +1154,40 @@ function detectName(email) {
1147
1154
  return detectNameFromEmail({ email });
1148
1155
  }
1149
1156
 
1157
+ exports.VerificationErrorCode = void 0;
1158
+ (function(VerificationErrorCode2) {
1159
+ VerificationErrorCode2["INVALID_FORMAT"] = "INVALID_FORMAT";
1160
+ VerificationErrorCode2["INVALID_DOMAIN"] = "INVALID_DOMAIN";
1161
+ VerificationErrorCode2["NO_MX_RECORDS"] = "NO_MX_RECORDS";
1162
+ VerificationErrorCode2["SMTP_CONNECTION_FAILED"] = "SMTP_CONNECTION_FAILED";
1163
+ VerificationErrorCode2["SMTP_TIMEOUT"] = "SMTP_TIMEOUT";
1164
+ VerificationErrorCode2["MAILBOX_NOT_FOUND"] = "MAILBOX_NOT_FOUND";
1165
+ VerificationErrorCode2["MAILBOX_FULL"] = "MAILBOX_FULL";
1166
+ VerificationErrorCode2["NETWORK_ERROR"] = "NETWORK_ERROR";
1167
+ VerificationErrorCode2["DISPOSABLE_EMAIL"] = "DISPOSABLE_EMAIL";
1168
+ VerificationErrorCode2["FREE_EMAIL_PROVIDER"] = "FREE_EMAIL_PROVIDER";
1169
+ })(exports.VerificationErrorCode || (exports.VerificationErrorCode = {}));
1170
+ exports.SMTPStep = void 0;
1171
+ (function(SMTPStep2) {
1172
+ SMTPStep2["GREETING"] = "GREETING";
1173
+ SMTPStep2["EHLO"] = "EHLO";
1174
+ SMTPStep2["HELO"] = "HELO";
1175
+ SMTPStep2["STARTTLS"] = "STARTTLS";
1176
+ SMTPStep2["MAIL_FROM"] = "MAIL_FROM";
1177
+ SMTPStep2["RCPT_TO"] = "RCPT_TO";
1178
+ SMTPStep2["VRFY"] = "VRFY";
1179
+ SMTPStep2["QUIT"] = "QUIT";
1180
+ })(exports.SMTPStep || (exports.SMTPStep = {}));
1181
+
1182
+ function isIPAddress(host) {
1183
+ const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
1184
+ if (ipv4Regex.test(host)) {
1185
+ const octets = host.split(".");
1186
+ return octets.every((octet) => parseInt(octet, 10) <= 255);
1187
+ }
1188
+ const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,7}:$|^(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}$|^(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}$|^(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}$|^[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})$|^::1(?::(?::[0-9a-fA-F]{1,4}){1,7})|$|:(?:(?::[0-9a-fA-F]{1,4}){1,7}:)$/;
1189
+ return ipv6Regex.test(host);
1190
+ }
1150
1191
  function isOverQuota(smtpReply) {
1151
1192
  return Boolean(smtpReply && /(over quota)/gi.test(smtpReply));
1152
1193
  }
@@ -1156,129 +1197,382 @@ function isInvalidMailboxError(smtpReply) {
1156
1197
  function isMultilineGreet(smtpReply) {
1157
1198
  return Boolean(smtpReply && /^(250|220)-/.test(smtpReply));
1158
1199
  }
1200
+ const DEFAULT_PORTS = [25, 587, 465];
1201
+ const DEFAULT_TIMEOUT = 3e3;
1202
+ const DEFAULT_MAX_RETRIES = 1;
1203
+ const PORT_CONFIGS = {
1204
+ 25: { tls: false, starttls: true },
1205
+ 587: { tls: false, starttls: true },
1206
+ 465: { tls: true, starttls: false }
1207
+ };
1159
1208
  async function verifyMailboxSMTP(params) {
1160
- const { local, domain, mxRecords = [], timeout, debug, port = 25, retryAttempts = 1 } = params;
1161
- const log = debug ? console.debug : (..._args) => {
1209
+ const { local, domain, mxRecords = [], options = {} } = params;
1210
+ const { ports = DEFAULT_PORTS, timeout = DEFAULT_TIMEOUT, maxRetries = DEFAULT_MAX_RETRIES, tls: tlsConfig = true, hostname = "localhost", useVRFY = true, cache, debug = false, sequence } = options;
1211
+ const log = debug ? (...args) => console.log("[SMTP]", ...args) : () => {
1162
1212
  };
1163
1213
  if (!mxRecords || mxRecords.length === 0) {
1164
- return false;
1214
+ log("No MX records found");
1215
+ return { result: false, cached: false, port: 0, portCached: false };
1216
+ }
1217
+ const mxHost = mxRecords[0];
1218
+ log(`Verifying ${local}@${domain} via ${mxHost}`);
1219
+ const smtpCacheStore = cache ? getCacheStore(cache, "smtp") : null;
1220
+ if (smtpCacheStore) {
1221
+ let cachedResult;
1222
+ try {
1223
+ cachedResult = await smtpCacheStore.get(`${mxHost}:${local}@${domain}`);
1224
+ if (cachedResult !== void 0) {
1225
+ log(`Using cached SMTP result: ${cachedResult}`);
1226
+ return {
1227
+ result: typeof cachedResult === "boolean" ? cachedResult : null,
1228
+ cached: true,
1229
+ port: 0,
1230
+ portCached: false
1231
+ };
1232
+ }
1233
+ } catch (_error) {
1234
+ cachedResult = void 0;
1235
+ }
1165
1236
  }
1166
- const mxIndex = 0;
1167
- const mxRecord = mxRecords[mxIndex];
1168
- for (let attempt = 0; attempt < retryAttempts; attempt++) {
1169
- const result = await attemptVerification({
1170
- mxRecord,
1171
- local,
1172
- domain,
1173
- port,
1174
- timeout,
1175
- log,
1176
- attempt
1177
- });
1178
- if (result !== null) {
1179
- return result;
1237
+ const smtpPortCacheStore = cache ? getCacheStore(cache, "smtpPort") : null;
1238
+ if (smtpPortCacheStore) {
1239
+ let cachedPort;
1240
+ try {
1241
+ cachedPort = await smtpPortCacheStore.get(mxHost);
1242
+ } catch (_error) {
1243
+ cachedPort = null;
1180
1244
  }
1181
- if (attempt < retryAttempts - 1) {
1182
- await new Promise((resolve) => setTimeout(resolve, Math.min(1e3 * (attempt + 1), 3e3)));
1245
+ if (cachedPort) {
1246
+ log(`Using cached port: ${cachedPort}`);
1247
+ const result = await testSMTPConnection({
1248
+ mxHost,
1249
+ port: cachedPort,
1250
+ local,
1251
+ domain,
1252
+ timeout,
1253
+ tlsConfig,
1254
+ hostname,
1255
+ useVRFY,
1256
+ sequence,
1257
+ log
1258
+ });
1259
+ if (smtpCacheStore && result !== void 0) {
1260
+ try {
1261
+ await smtpCacheStore.set(`${mxHost}:${local}@${domain}`, result);
1262
+ log(`Cached SMTP result ${result} for ${local}@${domain} via ${mxHost}`);
1263
+ } catch (_error) {
1264
+ }
1265
+ }
1266
+ return { result, cached: false, port: cachedPort, portCached: true };
1183
1267
  }
1184
1268
  }
1185
- return null;
1269
+ for (const port of ports) {
1270
+ log(`Testing port ${port}`);
1271
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
1272
+ if (attempt > 0) {
1273
+ const delay = Math.min(1e3 * 2 ** (attempt - 1), 5e3);
1274
+ log(`Retry ${attempt + 1}, waiting ${delay}ms`);
1275
+ await new Promise((resolve) => setTimeout(resolve, delay));
1276
+ }
1277
+ const result = await testSMTPConnection({
1278
+ mxHost,
1279
+ port,
1280
+ local,
1281
+ domain,
1282
+ timeout,
1283
+ tlsConfig,
1284
+ hostname,
1285
+ useVRFY,
1286
+ sequence,
1287
+ log
1288
+ });
1289
+ if (smtpCacheStore && result !== void 0) {
1290
+ try {
1291
+ await smtpCacheStore.set(`${mxHost}:${local}@${domain}`, result);
1292
+ log(`Cached SMTP result ${result} for ${local}@${domain} via ${mxHost}`);
1293
+ } catch (_error) {
1294
+ }
1295
+ }
1296
+ if (result !== null) {
1297
+ if (smtpPortCacheStore) {
1298
+ try {
1299
+ await smtpPortCacheStore.set(mxHost, port);
1300
+ log(`Cached port ${port} for ${mxHost}`);
1301
+ } catch (_error) {
1302
+ }
1303
+ }
1304
+ return { result, cached: false, port, portCached: false };
1305
+ }
1306
+ }
1307
+ }
1308
+ log("All ports failed");
1309
+ return { result: null, cached: false, port: 0, portCached: false };
1186
1310
  }
1187
- async function attemptVerification(params) {
1188
- const { mxRecord, local, domain, port, timeout, log, attempt } = params;
1311
+ async function testSMTPConnection(params) {
1312
+ const { mxHost, port, local, domain, timeout, tlsConfig, hostname, useVRFY, sequence, log } = params;
1313
+ const portConfig = PORT_CONFIGS[port] || { tls: false, starttls: false };
1314
+ const useTLS = tlsConfig !== false && (portConfig.tls || portConfig.starttls);
1315
+ const implicitTLS = portConfig.tls;
1316
+ const defaultSequence = {
1317
+ steps: [exports.SMTPStep.GREETING, exports.SMTPStep.EHLO, exports.SMTPStep.MAIL_FROM, exports.SMTPStep.RCPT_TO]
1318
+ };
1319
+ const activeSequence = sequence || defaultSequence;
1320
+ if (port === 25) {
1321
+ activeSequence.steps = activeSequence.steps.map((step) => step === exports.SMTPStep.EHLO ? exports.SMTPStep.HELO : step);
1322
+ }
1323
+ const tlsOptions = {
1324
+ host: mxHost,
1325
+ servername: isIPAddress(mxHost) ? void 0 : mxHost,
1326
+ // Don't set servername for IP addresses
1327
+ rejectUnauthorized: false,
1328
+ minVersion: "TLSv1.2",
1329
+ ...typeof tlsConfig === "object" ? tlsConfig : {}
1330
+ };
1189
1331
  return new Promise((resolve) => {
1190
- log(`[verifyMailboxSMTP] connecting to ${mxRecord}:${port} (attempt ${attempt + 1})`);
1191
- const socket = net__namespace.connect({
1192
- host: mxRecord,
1193
- port
1194
- });
1195
- let resTimeout = null;
1332
+ let socket;
1333
+ let buffer = "";
1334
+ let isTLS = implicitTLS;
1335
+ let currentStepIndex = 0;
1196
1336
  let resolved = false;
1197
- let cleaned = false;
1337
+ let supportsSTARTTLS = false;
1338
+ let supportsVRFY = false;
1198
1339
  const cleanup = () => {
1199
- if (cleaned)
1200
- return;
1201
- cleaned = true;
1202
- if (resTimeout) {
1203
- clearTimeout(resTimeout);
1204
- resTimeout = null;
1205
- }
1206
- if (socket && !socket.destroyed) {
1207
- socket.removeAllListeners();
1208
- socket.destroy();
1209
- }
1210
- };
1211
- const ret = (result) => {
1212
1340
  if (resolved)
1213
1341
  return;
1214
1342
  resolved = true;
1215
- if (!(socket === null || socket === void 0 ? void 0 : socket.destroyed)) {
1216
- log("[verifyMailboxSMTP] closing socket");
1343
+ try {
1217
1344
  socket === null || socket === void 0 ? void 0 : socket.write("QUIT\r\n");
1218
- socket === null || socket === void 0 ? void 0 : socket.end();
1345
+ } catch {
1219
1346
  }
1347
+ setTimeout(() => socket === null || socket === void 0 ? void 0 : socket.destroy(), 100);
1348
+ };
1349
+ const finish = (result, reason) => {
1350
+ if (resolved)
1351
+ return;
1352
+ log(`${port}: ${reason || (result ? "valid" : "invalid")}`);
1220
1353
  cleanup();
1221
1354
  resolve(result);
1222
1355
  };
1223
- const messages = [`HELO ${domain}`, `MAIL FROM: <${local}@${domain}>`, `RCPT TO: <${local}@${domain}>`];
1224
- log("[verifyMailboxSMTP] writing messages", messages);
1225
- socket.on("data", (data) => {
1226
- const dataString = String(data);
1227
- log("[verifyMailboxSMTP] got data", dataString);
1228
- if (isInvalidMailboxError(dataString))
1229
- return ret(false);
1230
- if (isOverQuota(dataString))
1231
- return ret(false);
1232
- if (!dataString.includes("220") && !dataString.includes("250"))
1233
- return ret(null);
1234
- if (isMultilineGreet(dataString))
1356
+ const sendCommand = (cmd) => {
1357
+ if (resolved)
1235
1358
  return;
1236
- if (messages.length > 0) {
1237
- const message = messages.shift();
1238
- log("[verifyMailboxSMTP] writing message", message);
1239
- return socket.write(`${message}\r
1359
+ log(`\u2192 ${cmd}`);
1360
+ socket === null || socket === void 0 ? void 0 : socket.write(`${cmd}\r
1240
1361
  `);
1362
+ };
1363
+ const nextStep = () => {
1364
+ currentStepIndex++;
1365
+ if (currentStepIndex >= activeSequence.steps.length) {
1366
+ finish(true, "sequence_complete");
1367
+ return;
1241
1368
  }
1242
- ret(true);
1243
- });
1244
- socket.on("error", (err) => {
1245
- log("[verifyMailboxSMTP] error in socket", err);
1246
- ret(null);
1247
- });
1248
- socket.on("close", (err) => {
1249
- if (!resolved) {
1250
- log("[verifyMailboxSMTP] close socket", err);
1251
- ret(null);
1369
+ executeStep(activeSequence.steps[currentStepIndex]);
1370
+ };
1371
+ const executeStep = (step) => {
1372
+ if (resolved)
1373
+ return;
1374
+ switch (step) {
1375
+ case exports.SMTPStep.EHLO:
1376
+ sendCommand(`EHLO ${hostname}`);
1377
+ break;
1378
+ case exports.SMTPStep.HELO:
1379
+ sendCommand(`HELO ${domain}`);
1380
+ break;
1381
+ case exports.SMTPStep.GREETING:
1382
+ break;
1383
+ case exports.SMTPStep.STARTTLS:
1384
+ sendCommand("STARTTLS");
1385
+ break;
1386
+ case exports.SMTPStep.MAIL_FROM: {
1387
+ const from = activeSequence.from || "<>";
1388
+ sendCommand(`MAIL FROM:${from}`);
1389
+ break;
1390
+ }
1391
+ case exports.SMTPStep.RCPT_TO:
1392
+ sendCommand(`RCPT TO:<${local}@${domain}>`);
1393
+ break;
1394
+ case exports.SMTPStep.VRFY: {
1395
+ const vrfyTarget = activeSequence.vrfyTarget || local;
1396
+ sendCommand(`VRFY ${vrfyTarget}`);
1397
+ break;
1398
+ }
1399
+ case exports.SMTPStep.QUIT:
1400
+ sendCommand("QUIT");
1401
+ break;
1252
1402
  }
1253
- });
1254
- socket.on("timeout", () => {
1255
- log("[verifyMailboxSMTP] timeout socket");
1256
- ret(null);
1257
- });
1258
- resTimeout = setTimeout(() => {
1259
- log(`[verifyMailboxSMTP] timed out (${timeout} ms)`);
1260
- if (!resolved) {
1261
- socket.destroy();
1262
- ret(null);
1403
+ };
1404
+ const processResponse = (response) => {
1405
+ if (resolved)
1406
+ return;
1407
+ const code = response.substring(0, 3);
1408
+ const isMultiline = response.length > 3 && response[3] === "-";
1409
+ log(`\u2190 ${response}`);
1410
+ if (isMultilineGreet(response)) {
1411
+ return;
1263
1412
  }
1264
- }, timeout);
1413
+ if (isOverQuota(response)) {
1414
+ finish(false, "over_quota");
1415
+ return;
1416
+ }
1417
+ if (isInvalidMailboxError(response)) {
1418
+ finish(false, "not_found");
1419
+ return;
1420
+ }
1421
+ if (isMultiline) {
1422
+ const currentStep2 = activeSequence.steps[currentStepIndex];
1423
+ if (currentStep2 === exports.SMTPStep.EHLO && code === "250") {
1424
+ const upper = response.toUpperCase();
1425
+ if (upper.includes("STARTTLS"))
1426
+ supportsSTARTTLS = true;
1427
+ if (upper.includes("VRFY"))
1428
+ supportsVRFY = true;
1429
+ }
1430
+ if (currentStep2 === exports.SMTPStep.HELO && code === "250") {
1431
+ const upper = response.toUpperCase();
1432
+ if (upper.includes("VRFY"))
1433
+ supportsVRFY = true;
1434
+ }
1435
+ return;
1436
+ }
1437
+ if (!response.includes("220") && !response.includes("250") && !response.includes("550") && !response.includes("552")) {
1438
+ finish(null, "unrecognized_response");
1439
+ return;
1440
+ }
1441
+ const currentStep = activeSequence.steps[currentStepIndex];
1442
+ switch (currentStep) {
1443
+ case exports.SMTPStep.GREETING:
1444
+ if (code.startsWith("220")) {
1445
+ nextStep();
1446
+ } else {
1447
+ finish(null, "no_greeting");
1448
+ }
1449
+ break;
1450
+ case exports.SMTPStep.EHLO:
1451
+ if (code.startsWith("250")) {
1452
+ const hasSTARTTLS = activeSequence.steps.includes(exports.SMTPStep.STARTTLS);
1453
+ if (!isTLS && useTLS && supportsSTARTTLS && !implicitTLS && hasSTARTTLS) {
1454
+ currentStepIndex = activeSequence.steps.indexOf(exports.SMTPStep.STARTTLS);
1455
+ executeStep(exports.SMTPStep.STARTTLS);
1456
+ } else {
1457
+ nextStep();
1458
+ }
1459
+ } else {
1460
+ finish(null, "ehlo_failed");
1461
+ }
1462
+ break;
1463
+ case exports.SMTPStep.HELO:
1464
+ if (code.startsWith("250")) {
1465
+ nextStep();
1466
+ } else {
1467
+ finish(null, "helo_failed");
1468
+ }
1469
+ break;
1470
+ case exports.SMTPStep.STARTTLS:
1471
+ if (code.startsWith("220")) {
1472
+ const plainSocket = socket;
1473
+ socket = tls__namespace.connect({
1474
+ ...tlsOptions,
1475
+ socket: plainSocket,
1476
+ servername: isIPAddress(mxHost) ? void 0 : mxHost
1477
+ }, () => {
1478
+ isTLS = true;
1479
+ log("TLS upgraded");
1480
+ buffer = "";
1481
+ const starttlsIndex = activeSequence.steps.indexOf(exports.SMTPStep.STARTTLS);
1482
+ currentStepIndex = starttlsIndex;
1483
+ nextStep();
1484
+ });
1485
+ socket.on("data", handleData);
1486
+ socket.on("error", () => finish(null, "tls_error"));
1487
+ } else {
1488
+ nextStep();
1489
+ }
1490
+ break;
1491
+ case exports.SMTPStep.MAIL_FROM:
1492
+ if (code.startsWith("250")) {
1493
+ nextStep();
1494
+ } else {
1495
+ finish(null, "mail_from_rejected");
1496
+ }
1497
+ break;
1498
+ case exports.SMTPStep.RCPT_TO:
1499
+ if (code.startsWith("250") || code.startsWith("251")) {
1500
+ finish(true, "valid");
1501
+ } else if (code.startsWith("552") || code.startsWith("452")) {
1502
+ finish(false, "over_quota");
1503
+ } else if (code.startsWith("4")) {
1504
+ finish(null, "temporary_failure");
1505
+ } else if (useVRFY && supportsVRFY && code.startsWith("5") && activeSequence.steps.includes(exports.SMTPStep.VRFY)) {
1506
+ currentStepIndex = activeSequence.steps.indexOf(exports.SMTPStep.VRFY);
1507
+ executeStep(exports.SMTPStep.VRFY);
1508
+ } else {
1509
+ finish(null, "ambiguous");
1510
+ }
1511
+ break;
1512
+ case exports.SMTPStep.VRFY:
1513
+ if (code.startsWith("250") || code.startsWith("252")) {
1514
+ finish(true, "vrfy_valid");
1515
+ } else if (code.startsWith("550")) {
1516
+ finish(false, "vrfy_invalid");
1517
+ } else {
1518
+ finish(null, "vrfy_failed");
1519
+ }
1520
+ break;
1521
+ case exports.SMTPStep.QUIT:
1522
+ if (code.startsWith("221")) {
1523
+ finish(null, "quit_received");
1524
+ }
1525
+ break;
1526
+ }
1527
+ };
1528
+ const handleData = (data) => {
1529
+ if (resolved)
1530
+ return;
1531
+ buffer += data.toString();
1532
+ let pos;
1533
+ while ((pos = buffer.indexOf("\r\n")) !== -1) {
1534
+ const line = buffer.substring(0, pos);
1535
+ buffer = buffer.substring(pos + 2);
1536
+ processResponse(line.trim());
1537
+ }
1538
+ };
1539
+ if (port < 0 || port > 65535 || !Number.isInteger(port)) {
1540
+ finish(null, "invalid_port");
1541
+ return;
1542
+ }
1543
+ if (implicitTLS) {
1544
+ const connectOptions = {
1545
+ ...tlsOptions,
1546
+ port,
1547
+ servername: isIPAddress(mxHost) ? void 0 : mxHost
1548
+ };
1549
+ socket = tls__namespace.connect(connectOptions, () => {
1550
+ log(`Connected to ${mxHost}:${port} with TLS`);
1551
+ socket.on("data", handleData);
1552
+ });
1553
+ } else {
1554
+ socket = net__namespace.connect({ host: mxHost, port }, () => {
1555
+ log(`Connected to ${mxHost}:${port}`);
1556
+ socket.on("data", handleData);
1557
+ });
1558
+ }
1559
+ if (activeSequence.steps.length === 0) {
1560
+ finish(true, "sequence_complete");
1561
+ return;
1562
+ }
1563
+ const firstStep = activeSequence.steps[0];
1564
+ if (firstStep !== exports.SMTPStep.GREETING) {
1565
+ executeStep(firstStep);
1566
+ }
1567
+ socket.setTimeout(timeout, () => finish(null, "timeout"));
1568
+ socket.on("error", () => finish(null, "connection_error"));
1569
+ socket.on("close", () => {
1570
+ if (!resolved)
1571
+ finish(null, "connection_closed");
1572
+ });
1265
1573
  });
1266
1574
  }
1267
1575
 
1268
- exports.VerificationErrorCode = void 0;
1269
- (function(VerificationErrorCode2) {
1270
- VerificationErrorCode2["INVALID_FORMAT"] = "INVALID_FORMAT";
1271
- VerificationErrorCode2["INVALID_DOMAIN"] = "INVALID_DOMAIN";
1272
- VerificationErrorCode2["NO_MX_RECORDS"] = "NO_MX_RECORDS";
1273
- VerificationErrorCode2["SMTP_CONNECTION_FAILED"] = "SMTP_CONNECTION_FAILED";
1274
- VerificationErrorCode2["SMTP_TIMEOUT"] = "SMTP_TIMEOUT";
1275
- VerificationErrorCode2["MAILBOX_NOT_FOUND"] = "MAILBOX_NOT_FOUND";
1276
- VerificationErrorCode2["MAILBOX_FULL"] = "MAILBOX_FULL";
1277
- VerificationErrorCode2["NETWORK_ERROR"] = "NETWORK_ERROR";
1278
- VerificationErrorCode2["DISPOSABLE_EMAIL"] = "DISPOSABLE_EMAIL";
1279
- VerificationErrorCode2["FREE_EMAIL_PROVIDER"] = "FREE_EMAIL_PROVIDER";
1280
- })(exports.VerificationErrorCode || (exports.VerificationErrorCode = {}));
1281
-
1282
1576
  async function isValidEmailDomain(emailOrDomain, cache) {
1283
1577
  let [_, emailDomain] = (emailOrDomain === null || emailOrDomain === void 0 ? void 0 : emailOrDomain.split("@")) || [];
1284
1578
  if (!emailDomain) {
@@ -2371,17 +2665,22 @@ async function verifyEmail(params) {
2371
2665
  domainPort = domainPorts[mxDomain.domain];
2372
2666
  }
2373
2667
  }
2374
- const smtpResult = await verifyMailboxSMTP({
2668
+ const { result: smtpResult, cached, portCached, port } = await verifyMailboxSMTP({
2375
2669
  local,
2376
2670
  domain,
2377
2671
  mxRecords,
2378
- timeout,
2379
- debug,
2380
- port: domainPort,
2381
- retryAttempts: params.retryAttempts
2672
+ options: {
2673
+ cache: params.cache,
2674
+ ports: domainPort ? [domainPort] : void 0,
2675
+ timeout,
2676
+ debug,
2677
+ maxRetries: params.retryAttempts
2678
+ }
2382
2679
  });
2383
2680
  await smtpCacheInstance.set(cacheKey, smtpResult);
2384
2681
  result.validSmtp = smtpResult;
2682
+ if (result.metadata)
2683
+ result.metadata.cached = cached;
2385
2684
  log(`[verifyEmail] SMTP verification result: ${result.validSmtp} for ${emailAddress} (cached for future use)`);
2386
2685
  }
2387
2686
  if (result.validSmtp === false && result.metadata) {