@emailcheck/email-validator-js 2.14.2 → 3.0.1-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.
Files changed (41) hide show
  1. package/README.md +279 -18
  2. package/dist/adapters/lru-adapter.d.ts +2 -2
  3. package/dist/adapters/redis-adapter.d.ts +4 -4
  4. package/dist/batch-verifier.d.ts +5 -0
  5. package/dist/cache-interface.d.ts +23 -15
  6. package/dist/cache.d.ts +7 -5
  7. package/dist/check-if-email-exists.d.ts +205 -0
  8. package/dist/domain-suggester.d.ts +6 -6
  9. package/dist/{validator.d.ts → email-validator.d.ts} +2 -2
  10. package/dist/email-verifier-types.d.ts +225 -0
  11. package/dist/index.d.ts +8 -8
  12. package/dist/index.esm.js +779 -266
  13. package/dist/index.esm.js.map +1 -1
  14. package/dist/index.js +783 -268
  15. package/dist/index.js.map +1 -1
  16. package/dist/mx-resolver.d.ts +2 -0
  17. package/dist/name-detector.d.ts +6 -6
  18. package/dist/serverless/adapters/aws-lambda.cjs.js.map +1 -1
  19. package/dist/serverless/adapters/aws-lambda.esm.js.map +1 -1
  20. package/dist/serverless/adapters/cloudflare.cjs.js.map +1 -1
  21. package/dist/serverless/adapters/cloudflare.esm.js.map +1 -1
  22. package/dist/serverless/adapters/vercel.cjs.js.map +1 -1
  23. package/dist/serverless/adapters/vercel.esm.js.map +1 -1
  24. package/dist/serverless/index.cjs.js.map +1 -1
  25. package/dist/serverless/index.d.ts +1 -1
  26. package/dist/serverless/index.esm.js.map +1 -1
  27. package/dist/serverless/{core.cjs.js → verifier.cjs.js} +1 -1
  28. package/dist/serverless/verifier.cjs.js.map +1 -0
  29. package/dist/serverless/{core.esm.js → verifier.esm.js} +1 -1
  30. package/dist/serverless/verifier.esm.js.map +1 -0
  31. package/dist/smtp-verifier.d.ts +7 -0
  32. package/dist/types.d.ts +170 -28
  33. package/dist/whois.d.ts +3 -3
  34. package/package.json +19 -19
  35. package/dist/batch.d.ts +0 -5
  36. package/dist/dns.d.ts +0 -2
  37. package/dist/serverless/core.cjs.js.map +0 -1
  38. package/dist/serverless/core.esm.js.map +0 -1
  39. package/dist/smtp.d.ts +0 -2
  40. /package/dist/serverless/{core.d.ts → verifier.d.ts} +0 -0
  41. /package/dist/serverless/{core.min.js → verifier.min.js} +0 -0
package/dist/index.esm.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { isValid, parse } from 'psl';
2
2
  import { lru } from 'tiny-lru';
3
- import { promises } from 'node:dns';
4
3
  import { stringSimilarity } from 'string-similarity-js';
4
+ import { promises } from 'node:dns';
5
5
  import * as net from 'node:net';
6
+ import * as tls from 'node:tls';
6
7
 
7
8
  class LRUAdapter {
8
9
  constructor(maxSize = 1e3, ttlMs = 36e5) {
@@ -43,28 +44,23 @@ class LRUAdapter {
43
44
  const DEFAULT_CACHE_OPTIONS = {
44
45
  ttl: {
45
46
  mx: 36e5,
46
- // 1 hour
47
47
  disposable: 864e5,
48
- // 24 hours
49
48
  free: 864e5,
50
- // 24 hours
51
49
  domainValid: 864e5,
52
- // 24 hours
53
50
  smtp: 18e5,
54
- // 30 minutes
51
+ smtpPort: 864e5,
55
52
  domainSuggestion: 864e5,
56
- // 24 hours
57
53
  whois: 36e5
58
- // 1 hour
59
54
  },
60
55
  maxSize: {
61
- mx: 500,
62
- disposable: 1e3,
63
- free: 1e3,
64
- domainValid: 1e3,
65
- smtp: 500,
66
- domainSuggestion: 1e3,
67
- whois: 200
56
+ mx: 1e4,
57
+ disposable: 1e4,
58
+ free: 1e4,
59
+ domainValid: 1e4,
60
+ smtp: 1e4,
61
+ smtpPort: 1e4,
62
+ domainSuggestion: 1e4,
63
+ whois: 1e4
68
64
  }
69
65
  };
70
66
  let defaultCacheInstance = null;
@@ -76,6 +72,7 @@ function getDefaultCache() {
76
72
  free: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.free, DEFAULT_CACHE_OPTIONS.ttl.free),
77
73
  domainValid: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.domainValid, DEFAULT_CACHE_OPTIONS.ttl.domainValid),
78
74
  smtp: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.smtp, DEFAULT_CACHE_OPTIONS.ttl.smtp),
75
+ smtpPort: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.smtpPort, DEFAULT_CACHE_OPTIONS.ttl.smtpPort),
79
76
  domainSuggestion: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.domainSuggestion, DEFAULT_CACHE_OPTIONS.ttl.domainSuggestion),
80
77
  whois: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.whois, DEFAULT_CACHE_OPTIONS.ttl.whois)
81
78
  };
@@ -92,6 +89,7 @@ function clearDefaultCache() {
92
89
  defaultCacheInstance.free.clear();
93
90
  defaultCacheInstance.domainValid.clear();
94
91
  defaultCacheInstance.smtp.clear();
92
+ defaultCacheInstance.smtpPort.clear();
95
93
  defaultCacheInstance.domainSuggestion.clear();
96
94
  defaultCacheInstance.whois.clear();
97
95
  }
@@ -100,41 +98,7 @@ function resetDefaultCache() {
100
98
  defaultCacheInstance = null;
101
99
  }
102
100
 
103
- async function resolveMxRecords(params) {
104
- const { domain, cache, logger } = params;
105
- const log = logger || (() => {
106
- });
107
- const cacheStore = getCacheStore(cache, "mx");
108
- const cached = await cacheStore.get(domain);
109
- if (cached !== null && cached !== void 0) {
110
- log(`[resolveMxRecords] Cache hit for ${domain}: ${cached.length} MX records`);
111
- return cached;
112
- }
113
- log(`[resolveMxRecords] Performing DNS MX lookup for ${domain}`);
114
- try {
115
- const records = await promises.resolveMx(domain);
116
- records.sort((a, b) => {
117
- if (a.priority < b.priority) {
118
- return -1;
119
- }
120
- if (a.priority > b.priority) {
121
- return 1;
122
- }
123
- return 0;
124
- });
125
- const exchanges = records.map((record) => record.exchange);
126
- log(`[resolveMxRecords] Found ${exchanges.length} MX records for ${domain}: [${exchanges.join(", ")}]`);
127
- await cacheStore.set(domain, exchanges);
128
- log(`[resolveMxRecords] Cached ${exchanges.length} MX records for ${domain}`);
129
- return exchanges;
130
- } catch (error) {
131
- log(`[resolveMxRecords] MX lookup failed for ${domain}, caching empty result`);
132
- await cacheStore.set(domain, []);
133
- throw error;
134
- }
135
- }
136
-
137
- const COMMON_EMAIL_DOMAINS = [
101
+ const commonEmailDomains = [
138
102
  // Popular free email providers
139
103
  "gmail.com",
140
104
  "yahoo.com",
@@ -219,7 +183,7 @@ function getSimilarityThreshold(domain) {
219
183
  return 0.75;
220
184
  }
221
185
  function defaultDomainSuggestionMethod(domain, commonDomains) {
222
- const domainsToCheck = commonDomains || COMMON_EMAIL_DOMAINS;
186
+ const domainsToCheck = commonDomains || commonEmailDomains;
223
187
  const lowerDomain = domain.toLowerCase();
224
188
  if (domainsToCheck.includes(lowerDomain)) {
225
189
  return null;
@@ -275,7 +239,7 @@ async function defaultDomainSuggestionMethodImpl(domain, commonDomains, cache) {
275
239
  if (!domain || domain.length < 3) {
276
240
  return null;
277
241
  }
278
- const domainsToCheck = commonDomains || COMMON_EMAIL_DOMAINS;
242
+ const domainsToCheck = commonDomains || commonEmailDomains;
279
243
  const lowerDomain = domain.toLowerCase();
280
244
  const cacheKey = `${lowerDomain}:${domainsToCheck.length}`;
281
245
  const cacheStore = getCacheStore(cache, "domainSuggestion");
@@ -371,15 +335,107 @@ async function suggestEmailDomain(email, commonDomains, cache) {
371
335
  return null;
372
336
  }
373
337
  function isCommonDomain(domain, commonDomains) {
374
- const domainsToCheck = commonDomains || COMMON_EMAIL_DOMAINS;
338
+ const domainsToCheck = commonDomains || commonEmailDomains;
375
339
  return domainsToCheck.includes(domain.toLowerCase());
376
340
  }
377
341
  function getDomainSimilarity(domain1, domain2) {
378
342
  return stringSimilarity(domain1.toLowerCase(), domain2.toLowerCase());
379
343
  }
380
344
 
381
- const NAME_SEPARATORS = [".", "_", "-"];
382
- const COMMON_NAME_SUFFIXES = [
345
+ async function isValidEmailDomain(emailOrDomain, cache) {
346
+ let [localPart, emailDomain] = (emailOrDomain === null || emailOrDomain === void 0 ? void 0 : emailOrDomain.split("@")) || [];
347
+ if (!emailDomain) {
348
+ emailDomain = localPart;
349
+ }
350
+ if (!emailDomain) {
351
+ return false;
352
+ }
353
+ const cacheStore = getCacheStore(cache, "domainValid");
354
+ const cached = await cacheStore.get(emailDomain);
355
+ if (cached !== null && cached !== void 0) {
356
+ return cached.isValid;
357
+ }
358
+ try {
359
+ const isValidResult = isValid(emailDomain) || false;
360
+ const richResult = {
361
+ isValid: isValidResult,
362
+ hasMX: false,
363
+ // MX not checked in this function
364
+ checkedAt: Date.now()
365
+ };
366
+ await cacheStore.set(emailDomain, richResult);
367
+ return isValidResult;
368
+ } catch (validationError) {
369
+ const errorResult = {
370
+ isValid: false,
371
+ hasMX: false,
372
+ checkedAt: Date.now()
373
+ };
374
+ await cacheStore.set(emailDomain, errorResult);
375
+ return false;
376
+ }
377
+ }
378
+ function isValidEmail(emailAddress) {
379
+ if (!emailAddress || typeof emailAddress !== "string") {
380
+ return false;
381
+ }
382
+ const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
383
+ const emailLower = emailAddress.toLowerCase();
384
+ if (emailLower.indexOf(".+") !== -1)
385
+ return false;
386
+ if (emailLower.indexOf("..") !== -1)
387
+ return false;
388
+ if (emailLower.startsWith(".") || emailLower.endsWith("."))
389
+ return false;
390
+ const parts = emailAddress.split("@");
391
+ if (parts.length !== 2)
392
+ return false;
393
+ const [localPart, domain] = parts;
394
+ if (!localPart || !domain)
395
+ return false;
396
+ if (localPart.length > 64)
397
+ return false;
398
+ if (domain.length > 253)
399
+ return false;
400
+ return re.test(emailLower);
401
+ }
402
+
403
+ async function resolveMxRecords(params) {
404
+ const { domain, cache, logger } = params;
405
+ const log = logger || (() => {
406
+ });
407
+ const cacheStore = getCacheStore(cache, "mx");
408
+ const cached = await cacheStore.get(domain);
409
+ if (cached !== null && cached !== void 0) {
410
+ log(`[resolveMxRecords] Cache hit for ${domain}: ${cached === null || cached === void 0 ? void 0 : cached.length} MX records`);
411
+ return cached;
412
+ }
413
+ log(`[resolveMxRecords] Performing DNS MX lookup for ${domain}`);
414
+ try {
415
+ const records = await promises.resolveMx(domain);
416
+ records === null || records === void 0 ? void 0 : records.sort((a, b) => {
417
+ if (a.priority < b.priority) {
418
+ return -1;
419
+ }
420
+ if (a.priority > b.priority) {
421
+ return 1;
422
+ }
423
+ return 0;
424
+ });
425
+ const exchanges = records === null || records === void 0 ? void 0 : records.map((record) => record.exchange);
426
+ log(`[resolveMxRecords] Found ${exchanges === null || exchanges === void 0 ? void 0 : exchanges.length} MX records for ${domain}: [${exchanges === null || exchanges === void 0 ? void 0 : exchanges.join(", ")}]`);
427
+ await cacheStore.set(domain, exchanges);
428
+ log(`[resolveMxRecords] Cached ${exchanges === null || exchanges === void 0 ? void 0 : exchanges.length} MX records for ${domain}`);
429
+ return exchanges;
430
+ } catch (error) {
431
+ log(`[resolveMxRecords] MX lookup failed for ${domain}, caching empty result`);
432
+ await cacheStore.set(domain, []);
433
+ throw error;
434
+ }
435
+ }
436
+
437
+ const nameSeparator = [".", "_", "-"];
438
+ const commonNameSuffixes = [
383
439
  "mail",
384
440
  "email",
385
441
  "contact",
@@ -394,7 +450,7 @@ const COMMON_NAME_SUFFIXES = [
394
450
  "notifications",
395
451
  "alerts"
396
452
  ];
397
- const CONTEXTUAL_SUFFIXES = [
453
+ const contextualSuffixes = [
398
454
  "dev",
399
455
  "company",
400
456
  "team",
@@ -426,7 +482,7 @@ const COMMON_TITLES = [
426
482
  "father",
427
483
  "sister"
428
484
  ];
429
- const COMMON_FIRST_NAMES = /* @__PURE__ */ new Set([
485
+ const commonFirstName = /* @__PURE__ */ new Set([
430
486
  // English names
431
487
  "james",
432
488
  "john",
@@ -604,7 +660,7 @@ const COMMON_FIRST_NAMES = /* @__PURE__ */ new Set([
604
660
  "sekou",
605
661
  "mariama"
606
662
  ]);
607
- const COMMON_LAST_NAMES = /* @__PURE__ */ new Set([
663
+ const commonLastName = /* @__PURE__ */ new Set([
608
664
  // English surnames
609
665
  "smith",
610
666
  "johnson",
@@ -724,19 +780,19 @@ function isYearLike(str) {
724
780
  return /^(19|20)\d{2}$/.test(str);
725
781
  }
726
782
  function isKnownFirstName(str) {
727
- return COMMON_FIRST_NAMES.has(str.toLowerCase());
783
+ return commonFirstName.has(str.toLowerCase());
728
784
  }
729
785
  function isKnownLastName(str) {
730
- return COMMON_LAST_NAMES.has(str.toLowerCase());
786
+ return commonLastName.has(str.toLowerCase());
731
787
  }
732
788
  function isTitle(str) {
733
789
  return COMMON_TITLES.includes(str.toLowerCase().replace(".", ""));
734
790
  }
735
791
  function getFirstNameScore(str) {
736
792
  const lower = str.toLowerCase();
737
- if (COMMON_FIRST_NAMES.has(lower))
793
+ if (commonFirstName.has(lower))
738
794
  return 1;
739
- if (COMMON_LAST_NAMES.has(lower))
795
+ if (commonLastName.has(lower))
740
796
  return 0.3;
741
797
  if (str.length >= 2 && str.length <= 15 && /^[a-zA-Z]+$/.test(str))
742
798
  return 0.5;
@@ -744,9 +800,9 @@ function getFirstNameScore(str) {
744
800
  }
745
801
  function getLastNameScore(str) {
746
802
  const lower = str.toLowerCase();
747
- if (COMMON_LAST_NAMES.has(lower))
803
+ if (commonLastName.has(lower))
748
804
  return 1;
749
- if (COMMON_FIRST_NAMES.has(lower))
805
+ if (commonFirstName.has(lower))
750
806
  return 0.3;
751
807
  if (str.length >= 2 && str.length <= 20 && /^[a-zA-Z]+$/.test(str))
752
808
  return 0.5;
@@ -855,7 +911,7 @@ function isLikelyName(str, allowNumbers = false, allowSingleLetter = false) {
855
911
  return false;
856
912
  if (str.length === 1 && allowSingleLetter && /^[a-zA-Z]$/.test(str))
857
913
  return true;
858
- if (COMMON_NAME_SUFFIXES.includes(str.toLowerCase()))
914
+ if (commonNameSuffixes.includes(str.toLowerCase()))
859
915
  return false;
860
916
  if (allowNumbers) {
861
917
  if (/^\d+$/.test(str))
@@ -917,7 +973,7 @@ function defaultNameDetectionMethod(email) {
917
973
  }
918
974
  }
919
975
  if (!firstName && !lastName) {
920
- for (const separator of NAME_SEPARATORS) {
976
+ for (const separator of nameSeparator) {
921
977
  if (cleanedLocal.includes(separator)) {
922
978
  const parts = cleanedLocal.split(separator).filter((p) => p.length > 0);
923
979
  if (parts.length === 2) {
@@ -979,7 +1035,7 @@ function defaultNameDetectionMethod(email) {
979
1035
  const firstParsed = parseCompositeNamePart(first);
980
1036
  const middleParsed = parseCompositeNamePart(middle);
981
1037
  const lastParsed = parseCompositeNamePart(last);
982
- const isLastSuffix = COMMON_NAME_SUFFIXES.includes(last.toLowerCase()) || CONTEXTUAL_SUFFIXES.includes(last.toLowerCase()) || isYearLike(last);
1038
+ const isLastSuffix = commonNameSuffixes.includes(last.toLowerCase()) || contextualSuffixes.includes(last.toLowerCase()) || isYearLike(last);
983
1039
  if (isLastSuffix) {
984
1040
  if (isLikelyName(first, true, true) && isLikelyName(middle, true, true)) {
985
1041
  const cleanedFirst = firstParsed.hasNumbers ? firstParsed.cleaned : first;
@@ -1012,7 +1068,7 @@ function defaultNameDetectionMethod(email) {
1012
1068
  } else if (parts.length > 3) {
1013
1069
  const firstPart = parts[0];
1014
1070
  const lastPartLower = parts[parts.length - 1].toLowerCase();
1015
- const isLastPartSuffix = COMMON_NAME_SUFFIXES.includes(lastPartLower) || CONTEXTUAL_SUFFIXES.includes(lastPartLower) || isYearLike(parts[parts.length - 1]);
1071
+ const isLastPartSuffix = commonNameSuffixes.includes(lastPartLower) || contextualSuffixes.includes(lastPartLower) || isYearLike(parts[parts.length - 1]);
1016
1072
  const effectiveLastIndex = isLastPartSuffix ? parts.length - 2 : parts.length - 1;
1017
1073
  const lastToUse = effectiveLastIndex >= 0 ? parts[effectiveLastIndex] : null;
1018
1074
  if (lastToUse && isLikelyName(firstPart, true, true) && isLikelyName(lastToUse, true, true)) {
@@ -1095,7 +1151,7 @@ function detectNameFromEmail(params) {
1095
1151
  }
1096
1152
  return defaultNameDetectionMethod(email);
1097
1153
  }
1098
- function cleanNameForAlgrothin(name) {
1154
+ function cleanNameForAlgorithm(name) {
1099
1155
  if (!name)
1100
1156
  return "";
1101
1157
  let cleaned = name.replace(/[._*]/g, "");
@@ -1105,13 +1161,13 @@ function cleanNameForAlgrothin(name) {
1105
1161
  }
1106
1162
  return cleaned;
1107
1163
  }
1108
- function detectNameForAlgrothin(email) {
1164
+ function detectNameForAlgorithm(email) {
1109
1165
  const detectedName = detectName(email);
1110
1166
  if (!detectedName) {
1111
1167
  return null;
1112
1168
  }
1113
- const cleanedFirstName = detectedName.firstName ? cleanNameForAlgrothin(detectedName.firstName) : void 0;
1114
- const cleanedLastName = detectedName.lastName ? cleanNameForAlgrothin(detectedName.lastName) : void 0;
1169
+ const cleanedFirstName = detectedName.firstName ? cleanNameForAlgorithm(detectedName.firstName) : void 0;
1170
+ const cleanedLastName = detectedName.lastName ? cleanNameForAlgorithm(detectedName.lastName) : void 0;
1115
1171
  if (!cleanedFirstName && !cleanedLastName) {
1116
1172
  return null;
1117
1173
  }
@@ -1126,6 +1182,156 @@ function detectName(email) {
1126
1182
  return detectNameFromEmail({ email });
1127
1183
  }
1128
1184
 
1185
+ var VerificationErrorCode;
1186
+ (function(VerificationErrorCode2) {
1187
+ VerificationErrorCode2["invalidFormat"] = "INVALID_FORMAT";
1188
+ VerificationErrorCode2["invalidDomain"] = "INVALID_DOMAIN";
1189
+ VerificationErrorCode2["noMxRecords"] = "NO_MX_RECORDS";
1190
+ VerificationErrorCode2["smtpConnectionFailed"] = "SMTP_CONNECTION_FAILED";
1191
+ VerificationErrorCode2["smtpTimeout"] = "SMTP_TIMEOUT";
1192
+ VerificationErrorCode2["mailboxNotFound"] = "MAILBOX_NOT_FOUND";
1193
+ VerificationErrorCode2["mailboxFull"] = "MAILBOX_FULL";
1194
+ VerificationErrorCode2["networkError"] = "NETWORK_ERROR";
1195
+ VerificationErrorCode2["disposableEmail"] = "DISPOSABLE_EMAIL";
1196
+ VerificationErrorCode2["freeEmailProvider"] = "FREE_EMAIL_PROVIDER";
1197
+ })(VerificationErrorCode || (VerificationErrorCode = {}));
1198
+ var EmailProvider;
1199
+ (function(EmailProvider2) {
1200
+ EmailProvider2["gmail"] = "gmail";
1201
+ EmailProvider2["hotmailB2b"] = "hotmail_b2b";
1202
+ EmailProvider2["hotmailB2c"] = "hotmail_b2c";
1203
+ EmailProvider2["proofpoint"] = "proofpoint";
1204
+ EmailProvider2["mimecast"] = "mimecast";
1205
+ EmailProvider2["yahoo"] = "yahoo";
1206
+ EmailProvider2["everythingElse"] = "everything_else";
1207
+ })(EmailProvider || (EmailProvider = {}));
1208
+ function parseSmtpError(errorMessage) {
1209
+ const lowerError = errorMessage.toLowerCase();
1210
+ const networkErrorPatterns = [
1211
+ "etimedout",
1212
+ "econnrefused",
1213
+ "enotfound",
1214
+ "econnreset",
1215
+ "socket hang up",
1216
+ "connection_timeout",
1217
+ "socket_timeout",
1218
+ "connection_error",
1219
+ "connection_closed"
1220
+ ];
1221
+ const isNetworkError = networkErrorPatterns.some((pattern) => lowerError.includes(pattern));
1222
+ if (isNetworkError) {
1223
+ return {
1224
+ isDisabled: false,
1225
+ hasFullInbox: false,
1226
+ isInvalid: true,
1227
+ isCatchAll: false
1228
+ };
1229
+ }
1230
+ const disabledPatterns = [
1231
+ "account disabled",
1232
+ "account is disabled",
1233
+ "user disabled",
1234
+ "user is disabled",
1235
+ "account locked",
1236
+ "account is locked",
1237
+ "user blocked",
1238
+ "user is blocked",
1239
+ "mailbox disabled",
1240
+ "delivery not authorized",
1241
+ "message rejected",
1242
+ "access denied",
1243
+ "permission denied",
1244
+ "recipient unknown",
1245
+ "recipient address rejected",
1246
+ "user unknown",
1247
+ "address unknown",
1248
+ "invalid recipient",
1249
+ "not a valid recipient",
1250
+ "recipient does not exist",
1251
+ "no such user",
1252
+ "user does not exist",
1253
+ "mailbox unavailable",
1254
+ "recipient unavailable",
1255
+ "address rejected",
1256
+ "550",
1257
+ "551",
1258
+ "553",
1259
+ "not_found",
1260
+ "ambiguous"
1261
+ ];
1262
+ const fullInboxPatterns = [
1263
+ "mailbox full",
1264
+ "inbox full",
1265
+ "quota exceeded",
1266
+ "over quota",
1267
+ "storage limit exceeded",
1268
+ "message too large",
1269
+ "insufficient storage",
1270
+ "mailbox over quota",
1271
+ "over the quota",
1272
+ "mailbox size limit exceeded",
1273
+ "account over quota",
1274
+ "storage space",
1275
+ "overquota",
1276
+ "452",
1277
+ "552",
1278
+ "over_quota"
1279
+ ];
1280
+ const catchAllPatterns = [
1281
+ "accept all mail",
1282
+ "catch-all",
1283
+ "catchall",
1284
+ "wildcard",
1285
+ "accepts any recipient",
1286
+ "recipient address accepted"
1287
+ ];
1288
+ const rateLimitPatterns = [
1289
+ "receiving mail at a rate that",
1290
+ "rate limit",
1291
+ "too many messages",
1292
+ "temporarily rejected",
1293
+ "try again later",
1294
+ "greylisted",
1295
+ "greylist",
1296
+ "deferring",
1297
+ "temporarily deferred",
1298
+ "421",
1299
+ "450",
1300
+ "451",
1301
+ "temporary_failure"
1302
+ ];
1303
+ const isDisabled = disabledPatterns.some((pattern) => lowerError.includes(pattern)) || lowerError.startsWith("550") || lowerError.startsWith("551") || lowerError.startsWith("553");
1304
+ const hasFullInbox = fullInboxPatterns.some((pattern) => lowerError.includes(pattern)) || lowerError.startsWith("452") || lowerError.startsWith("552");
1305
+ const isCatchAll = catchAllPatterns.some((pattern) => lowerError.includes(pattern));
1306
+ const isInvalid = !isDisabled && !hasFullInbox && !isCatchAll && !rateLimitPatterns.some((pattern) => lowerError.includes(pattern)) && !lowerError.startsWith("421") && !lowerError.startsWith("450") && !lowerError.startsWith("451");
1307
+ return {
1308
+ isDisabled,
1309
+ hasFullInbox,
1310
+ isInvalid,
1311
+ isCatchAll
1312
+ };
1313
+ }
1314
+ var SMTPStep;
1315
+ (function(SMTPStep2) {
1316
+ SMTPStep2["greeting"] = "GREETING";
1317
+ SMTPStep2["ehlo"] = "EHLO";
1318
+ SMTPStep2["helo"] = "HELO";
1319
+ SMTPStep2["startTls"] = "STARTTLS";
1320
+ SMTPStep2["mailFrom"] = "MAIL_FROM";
1321
+ SMTPStep2["rcptTo"] = "RCPT_TO";
1322
+ SMTPStep2["vrfy"] = "VRFY";
1323
+ SMTPStep2["quit"] = "QUIT";
1324
+ })(SMTPStep || (SMTPStep = {}));
1325
+
1326
+ function isIPAddress(host) {
1327
+ const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
1328
+ if (ipv4Regex.test(host)) {
1329
+ const octets = host.split(".");
1330
+ return octets.every((octet) => parseInt(octet, 10) <= 255);
1331
+ }
1332
+ 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}:)$/;
1333
+ return ipv6Regex.test(host);
1334
+ }
1129
1335
  function isOverQuota(smtpReply) {
1130
1336
  return Boolean(smtpReply && /(over quota)/gi.test(smtpReply));
1131
1337
  }
@@ -1135,182 +1341,469 @@ function isInvalidMailboxError(smtpReply) {
1135
1341
  function isMultilineGreet(smtpReply) {
1136
1342
  return Boolean(smtpReply && /^(250|220)-/.test(smtpReply));
1137
1343
  }
1344
+ const DEFAULT_PORTS = [25, 587, 465];
1345
+ const DEFAULT_TIMEOUT = 3e3;
1346
+ const DEFAULT_MAX_RETRIES = 1;
1347
+ const PORT_CONFIGS = {
1348
+ 25: { tls: false, starttls: true },
1349
+ 587: { tls: false, starttls: true },
1350
+ 465: { tls: true, starttls: false }
1351
+ };
1138
1352
  async function verifyMailboxSMTP(params) {
1139
- const { local, domain, mxRecords = [], timeout, debug, port = 25, retryAttempts = 1 } = params;
1140
- const log = debug ? console.debug : (..._args) => {
1353
+ const { local, domain, mxRecords = [], options = {} } = params;
1354
+ const { ports = DEFAULT_PORTS, timeout = DEFAULT_TIMEOUT, maxRetries = DEFAULT_MAX_RETRIES, tls: tlsConfig = true, hostname = "localhost", useVRFY = true, cache, debug = false, sequence } = options;
1355
+ const log = debug ? (...args) => console.log("[SMTP]", ...args) : () => {
1356
+ };
1357
+ const createSmtpResult = (result, port, tlsUsed, mxHost2) => {
1358
+ const reason = result === true ? "valid" : result === null ? "ambiguous" : "not_found";
1359
+ const parsedError = parseSmtpError(reason);
1360
+ return {
1361
+ canConnectSmtp: result !== null,
1362
+ hasFullInbox: parsedError.hasFullInbox,
1363
+ isCatchAll: parsedError.isCatchAll,
1364
+ isDeliverable: result === true,
1365
+ isDisabled: result === false && parsedError.isDisabled,
1366
+ error: result === null ? reason : result === false ? reason : void 0,
1367
+ providerUsed: EmailProvider.everythingElse,
1368
+ checkedAt: Date.now()
1369
+ };
1141
1370
  };
1371
+ const createFailureResult = (error) => ({
1372
+ canConnectSmtp: false,
1373
+ hasFullInbox: false,
1374
+ isCatchAll: false,
1375
+ isDeliverable: false,
1376
+ isDisabled: false,
1377
+ error,
1378
+ providerUsed: EmailProvider.everythingElse,
1379
+ checkedAt: Date.now()
1380
+ });
1142
1381
  if (!mxRecords || mxRecords.length === 0) {
1143
- return false;
1382
+ log("No MX records found");
1383
+ return {
1384
+ smtpResult: createFailureResult("No MX records found"),
1385
+ cached: false,
1386
+ port: 0,
1387
+ portCached: false
1388
+ };
1144
1389
  }
1145
- const mxIndex = 0;
1146
- const mxRecord = mxRecords[mxIndex];
1147
- for (let attempt = 0; attempt < retryAttempts; attempt++) {
1148
- const result = await attemptVerification({
1149
- mxRecord,
1150
- local,
1151
- domain,
1152
- port,
1153
- timeout,
1154
- log,
1155
- attempt
1156
- });
1157
- if (result !== null) {
1158
- return result;
1390
+ const mxHost = mxRecords[0];
1391
+ log(`Verifying ${local}@${domain} via ${mxHost}`);
1392
+ const smtpCacheStore = cache ? getCacheStore(cache, "smtp") : null;
1393
+ if (smtpCacheStore) {
1394
+ let cachedResult;
1395
+ try {
1396
+ cachedResult = await smtpCacheStore.get(`${mxHost}:${local}@${domain}`);
1397
+ if (cachedResult !== void 0 && cachedResult !== null) {
1398
+ log(`Using cached SMTP result: ${cachedResult.isDeliverable}`);
1399
+ return {
1400
+ smtpResult: cachedResult,
1401
+ cached: true,
1402
+ port: 0,
1403
+ portCached: false
1404
+ };
1405
+ }
1406
+ } catch (ignoredError) {
1407
+ cachedResult = void 0;
1408
+ }
1409
+ }
1410
+ const smtpPortCacheStore = cache ? getCacheStore(cache, "smtpPort") : null;
1411
+ if (smtpPortCacheStore) {
1412
+ let cachedPort;
1413
+ try {
1414
+ cachedPort = await smtpPortCacheStore.get(mxHost);
1415
+ } catch (ignoredError) {
1416
+ cachedPort = null;
1159
1417
  }
1160
- if (attempt < retryAttempts - 1) {
1161
- await new Promise((resolve) => setTimeout(resolve, Math.min(1e3 * (attempt + 1), 3e3)));
1418
+ if (cachedPort) {
1419
+ log(`Using cached port: ${cachedPort}`);
1420
+ const result = await testSMTPConnection({
1421
+ mxHost,
1422
+ port: cachedPort,
1423
+ local,
1424
+ domain,
1425
+ timeout,
1426
+ tlsConfig,
1427
+ hostname,
1428
+ useVRFY,
1429
+ sequence,
1430
+ log
1431
+ });
1432
+ const smtpResult = createSmtpResult(result);
1433
+ if (smtpCacheStore) {
1434
+ try {
1435
+ await smtpCacheStore.set(`${mxHost}:${local}@${domain}`, smtpResult);
1436
+ log(`Cached SMTP result ${result} for ${local}@${domain} via ${mxHost}`);
1437
+ } catch (ignoredError) {
1438
+ }
1439
+ }
1440
+ return { smtpResult, cached: false, port: cachedPort, portCached: true };
1162
1441
  }
1163
1442
  }
1164
- return null;
1443
+ for (const port of ports) {
1444
+ log(`Testing port ${port}`);
1445
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
1446
+ if (attempt > 0) {
1447
+ const delay = Math.min(200 * 2 ** (attempt - 1), 800);
1448
+ log(`Retry ${attempt + 1}, waiting ${delay}ms`);
1449
+ await new Promise((resolve) => setTimeout(resolve, delay));
1450
+ }
1451
+ const result = await testSMTPConnection({
1452
+ mxHost,
1453
+ port,
1454
+ local,
1455
+ domain,
1456
+ timeout,
1457
+ tlsConfig,
1458
+ hostname,
1459
+ useVRFY,
1460
+ sequence,
1461
+ log
1462
+ });
1463
+ const smtpResult = createSmtpResult(result);
1464
+ if (smtpCacheStore) {
1465
+ try {
1466
+ await smtpCacheStore.set(`${mxHost}:${local}@${domain}`, smtpResult);
1467
+ log(`Cached SMTP result ${result} for ${local}@${domain} via ${mxHost}`);
1468
+ } catch (ignoredError) {
1469
+ }
1470
+ }
1471
+ if (result !== null) {
1472
+ if (smtpPortCacheStore) {
1473
+ try {
1474
+ await smtpPortCacheStore.set(mxHost, port);
1475
+ log(`Cached port ${port} for ${mxHost}`);
1476
+ } catch (ignoredError) {
1477
+ }
1478
+ }
1479
+ return { smtpResult, cached: false, port, portCached: false };
1480
+ }
1481
+ }
1482
+ }
1483
+ log("All ports failed");
1484
+ return {
1485
+ smtpResult: createFailureResult("All SMTP connection attempts failed"),
1486
+ cached: false,
1487
+ port: 0,
1488
+ portCached: false
1489
+ };
1165
1490
  }
1166
- async function attemptVerification(params) {
1167
- const { mxRecord, local, domain, port, timeout, log, attempt } = params;
1491
+ async function testSMTPConnection(params) {
1492
+ const { mxHost, port, local, domain, timeout, tlsConfig, hostname, useVRFY, sequence, log } = params;
1493
+ const portConfig = PORT_CONFIGS[port] || { tls: false, starttls: false };
1494
+ const useTLS = tlsConfig !== false && (portConfig.tls || portConfig.starttls);
1495
+ const implicitTLS = portConfig.tls;
1496
+ const defaultSequence = {
1497
+ steps: [SMTPStep.greeting, SMTPStep.ehlo, SMTPStep.mailFrom, SMTPStep.rcptTo]
1498
+ };
1499
+ const activeSequence = sequence || defaultSequence;
1500
+ if (port === 25) {
1501
+ activeSequence.steps = activeSequence.steps.map((step) => step === SMTPStep.ehlo ? SMTPStep.helo : step);
1502
+ }
1503
+ const tlsOptions = {
1504
+ host: mxHost,
1505
+ servername: isIPAddress(mxHost) ? void 0 : mxHost,
1506
+ // Don't set servername for IP addresses
1507
+ rejectUnauthorized: false,
1508
+ minVersion: "TLSv1.2",
1509
+ ...typeof tlsConfig === "object" ? tlsConfig : {}
1510
+ };
1168
1511
  return new Promise((resolve) => {
1169
- log(`[verifyMailboxSMTP] connecting to ${mxRecord}:${port} (attempt ${attempt + 1})`);
1170
- const socket = net.connect({
1171
- host: mxRecord,
1172
- port
1173
- });
1174
- let resTimeout = null;
1512
+ let socket;
1513
+ let buffer = "";
1514
+ let isTLS = implicitTLS;
1515
+ let currentStepIndex = 0;
1175
1516
  let resolved = false;
1176
- let cleaned = false;
1177
- const cleanup = () => {
1178
- if (cleaned)
1179
- return;
1180
- cleaned = true;
1181
- if (resTimeout) {
1182
- clearTimeout(resTimeout);
1183
- resTimeout = null;
1184
- }
1185
- if (socket && !socket.destroyed) {
1186
- socket.removeAllListeners();
1187
- socket.destroy();
1188
- }
1189
- };
1190
- const ret = (result) => {
1517
+ let supportsSTARTTLS = false;
1518
+ let supportsVRFY = false;
1519
+ let cleanup = () => {
1191
1520
  if (resolved)
1192
1521
  return;
1193
1522
  resolved = true;
1194
- if (!(socket === null || socket === void 0 ? void 0 : socket.destroyed)) {
1195
- log("[verifyMailboxSMTP] closing socket");
1523
+ try {
1196
1524
  socket === null || socket === void 0 ? void 0 : socket.write("QUIT\r\n");
1197
- socket === null || socket === void 0 ? void 0 : socket.end();
1525
+ } catch {
1198
1526
  }
1527
+ setTimeout(() => socket === null || socket === void 0 ? void 0 : socket.destroy(), 100);
1528
+ };
1529
+ const finish = (result, reason) => {
1530
+ if (resolved)
1531
+ return;
1532
+ log(`${port}: ${reason || (result ? "valid" : "invalid")}`);
1199
1533
  cleanup();
1200
1534
  resolve(result);
1201
1535
  };
1202
- const messages = [`HELO ${domain}`, `MAIL FROM: <>`, `RCPT TO: <${local}@${domain}>`];
1203
- log("[verifyMailboxSMTP] writing messages", messages);
1204
- socket.on("data", (data) => {
1205
- const dataString = String(data);
1206
- log("[verifyMailboxSMTP] got data", dataString);
1207
- if (isInvalidMailboxError(dataString)) {
1208
- log("[verifyMailboxSMTP] invalid mailbox error detected");
1209
- return ret(false);
1536
+ const sendCommand = (cmd) => {
1537
+ if (resolved)
1538
+ return;
1539
+ log(`\u2192 ${cmd}`);
1540
+ socket === null || socket === void 0 ? void 0 : socket.write(`${cmd}\r
1541
+ `);
1542
+ };
1543
+ const nextStep = () => {
1544
+ currentStepIndex++;
1545
+ if (currentStepIndex >= activeSequence.steps.length) {
1546
+ finish(true, "sequence_complete");
1547
+ return;
1210
1548
  }
1211
- if (isOverQuota(dataString)) {
1212
- log("[verifyMailboxSMTP] mailbox over quota");
1213
- return ret(false);
1549
+ executeStep(activeSequence.steps[currentStepIndex]);
1550
+ };
1551
+ const executeStep = (step) => {
1552
+ if (resolved)
1553
+ return;
1554
+ switch (step) {
1555
+ case SMTPStep.ehlo:
1556
+ sendCommand(`EHLO ${hostname}`);
1557
+ break;
1558
+ case SMTPStep.helo:
1559
+ sendCommand(`HELO ${domain}`);
1560
+ break;
1561
+ case SMTPStep.greeting:
1562
+ break;
1563
+ case SMTPStep.startTls:
1564
+ sendCommand("STARTTLS");
1565
+ break;
1566
+ case SMTPStep.mailFrom: {
1567
+ const from = activeSequence.from || "<>";
1568
+ sendCommand(`MAIL FROM:${from}`);
1569
+ break;
1570
+ }
1571
+ case SMTPStep.rcptTo:
1572
+ sendCommand(`RCPT TO:<${local}@${domain}>`);
1573
+ break;
1574
+ case SMTPStep.vrfy: {
1575
+ const vrfyTarget = activeSequence.vrfyTarget || local;
1576
+ sendCommand(`VRFY ${vrfyTarget}`);
1577
+ break;
1578
+ }
1579
+ case SMTPStep.quit:
1580
+ sendCommand("QUIT");
1581
+ break;
1214
1582
  }
1215
- if (!dataString.includes("220") && !dataString.includes("250")) {
1216
- log("[verifyMailboxSMTP] unrecognized response, returning null");
1217
- return ret(null);
1583
+ };
1584
+ const processResponse = (response) => {
1585
+ if (resolved)
1586
+ return;
1587
+ const code = response.substring(0, 3);
1588
+ const isMultiline = response.length > 3 && response[3] === "-";
1589
+ log(`\u2190 ${response}`);
1590
+ if (isMultilineGreet(response)) {
1591
+ return;
1218
1592
  }
1219
- if (isMultilineGreet(dataString))
1593
+ if (isOverQuota(response)) {
1594
+ finish(false, "over_quota");
1220
1595
  return;
1221
- if (messages.length > 0) {
1222
- const message = messages.shift();
1223
- log("[verifyMailboxSMTP] writing message", message);
1224
- return socket.write(`${message}\r
1225
- `);
1226
1596
  }
1227
- ret(true);
1228
- });
1229
- socket.on("error", (err) => {
1230
- log("[verifyMailboxSMTP] error in socket", err);
1231
- ret(null);
1232
- });
1233
- socket.on("close", (err) => {
1597
+ if (isInvalidMailboxError(response)) {
1598
+ finish(false, "not_found");
1599
+ return;
1600
+ }
1601
+ if (isMultiline) {
1602
+ const currentStep2 = activeSequence.steps[currentStepIndex];
1603
+ if (currentStep2 === SMTPStep.ehlo && code === "250") {
1604
+ const upper = response.toUpperCase();
1605
+ if (upper.includes("STARTTLS"))
1606
+ supportsSTARTTLS = true;
1607
+ if (upper.includes("VRFY"))
1608
+ supportsVRFY = true;
1609
+ }
1610
+ if (currentStep2 === SMTPStep.helo && code === "250") {
1611
+ const upper = response.toUpperCase();
1612
+ if (upper.includes("VRFY"))
1613
+ supportsVRFY = true;
1614
+ }
1615
+ return;
1616
+ }
1617
+ if (!response.includes("220") && !response.includes("250") && !response.includes("550") && !response.includes("552")) {
1618
+ finish(null, "unrecognized_response");
1619
+ return;
1620
+ }
1621
+ const currentStep = activeSequence.steps[currentStepIndex];
1622
+ switch (currentStep) {
1623
+ case SMTPStep.greeting:
1624
+ if (code.startsWith("220")) {
1625
+ nextStep();
1626
+ } else {
1627
+ finish(null, "no_greeting");
1628
+ }
1629
+ break;
1630
+ case SMTPStep.ehlo:
1631
+ if (code.startsWith("250")) {
1632
+ const hasSTARTTLS = activeSequence.steps.includes(SMTPStep.startTls);
1633
+ if (!isTLS && useTLS && supportsSTARTTLS && !implicitTLS && hasSTARTTLS) {
1634
+ currentStepIndex = activeSequence.steps.indexOf(SMTPStep.startTls);
1635
+ executeStep(SMTPStep.startTls);
1636
+ } else {
1637
+ nextStep();
1638
+ }
1639
+ } else {
1640
+ finish(null, "ehlo_failed");
1641
+ }
1642
+ break;
1643
+ case SMTPStep.helo:
1644
+ if (code.startsWith("250")) {
1645
+ nextStep();
1646
+ } else {
1647
+ finish(null, "helo_failed");
1648
+ }
1649
+ break;
1650
+ case SMTPStep.startTls:
1651
+ if (code.startsWith("220")) {
1652
+ const plainSocket = socket;
1653
+ socket = tls.connect({
1654
+ ...tlsOptions,
1655
+ socket: plainSocket,
1656
+ servername: isIPAddress(mxHost) ? void 0 : mxHost
1657
+ }, () => {
1658
+ isTLS = true;
1659
+ log("TLS upgraded");
1660
+ buffer = "";
1661
+ const starttlsIndex = activeSequence.steps.indexOf(SMTPStep.startTls);
1662
+ currentStepIndex = starttlsIndex;
1663
+ nextStep();
1664
+ });
1665
+ socket.on("data", handleData);
1666
+ socket.on("error", () => finish(null, "tls_error"));
1667
+ } else {
1668
+ nextStep();
1669
+ }
1670
+ break;
1671
+ case SMTPStep.mailFrom:
1672
+ if (code.startsWith("250")) {
1673
+ nextStep();
1674
+ } else {
1675
+ finish(null, "mail_from_rejected");
1676
+ }
1677
+ break;
1678
+ case SMTPStep.rcptTo:
1679
+ if (code.startsWith("250") || code.startsWith("251")) {
1680
+ finish(true, "valid");
1681
+ } else if (code.startsWith("552") || code.startsWith("452")) {
1682
+ finish(false, "over_quota");
1683
+ } else if (code.startsWith("4")) {
1684
+ finish(null, "temporary_failure");
1685
+ } else if (useVRFY && supportsVRFY && code.startsWith("5") && activeSequence.steps.includes(SMTPStep.vrfy)) {
1686
+ currentStepIndex = activeSequence.steps.indexOf(SMTPStep.vrfy);
1687
+ executeStep(SMTPStep.vrfy);
1688
+ } else {
1689
+ finish(null, "ambiguous");
1690
+ }
1691
+ break;
1692
+ case SMTPStep.vrfy:
1693
+ if (code.startsWith("250") || code.startsWith("252")) {
1694
+ finish(true, "vrfy_valid");
1695
+ } else if (code.startsWith("550")) {
1696
+ finish(false, "vrfy_invalid");
1697
+ } else {
1698
+ finish(null, "vrfy_failed");
1699
+ }
1700
+ break;
1701
+ case SMTPStep.quit:
1702
+ if (code.startsWith("221")) {
1703
+ finish(null, "quit_received");
1704
+ }
1705
+ break;
1706
+ }
1707
+ };
1708
+ let handleData = (data) => {
1709
+ if (resolved)
1710
+ return;
1711
+ buffer += data.toString();
1712
+ let pos;
1713
+ while ((pos = buffer.indexOf("\r\n")) !== -1) {
1714
+ const line = buffer.substring(0, pos);
1715
+ buffer = buffer.substring(pos + 2);
1716
+ processResponse(line.trim());
1717
+ }
1718
+ };
1719
+ if (port < 0 || port > 65535 || !Number.isInteger(port)) {
1720
+ finish(null, "invalid_port");
1721
+ return;
1722
+ }
1723
+ if (implicitTLS) {
1724
+ const connectOptions = {
1725
+ ...tlsOptions,
1726
+ port,
1727
+ servername: isIPAddress(mxHost) ? void 0 : mxHost
1728
+ };
1729
+ socket = tls.connect(connectOptions, () => {
1730
+ log(`Connected to ${mxHost}:${port} with TLS`);
1731
+ socket.on("data", handleData);
1732
+ });
1733
+ } else {
1734
+ socket = net.connect({ host: mxHost, port }, () => {
1735
+ log(`Connected to ${mxHost}:${port}`);
1736
+ socket.on("data", handleData);
1737
+ });
1738
+ }
1739
+ if (activeSequence.steps.length === 0) {
1740
+ finish(true, "sequence_complete");
1741
+ return;
1742
+ }
1743
+ const firstStep = activeSequence.steps[0];
1744
+ let connectionTimeout;
1745
+ let stepTimeout;
1746
+ const resetActivityTimeout = () => {
1747
+ if (stepTimeout) {
1748
+ clearTimeout(stepTimeout);
1749
+ }
1750
+ stepTimeout = setTimeout(() => {
1751
+ if (!resolved) {
1752
+ log(`Step timeout after ${timeout}ms of inactivity`);
1753
+ finish(null, "step_timeout");
1754
+ }
1755
+ }, timeout);
1756
+ };
1757
+ connectionTimeout = setTimeout(() => {
1758
+ if (!resolved) {
1759
+ log(`Connection timeout after ${timeout}ms`);
1760
+ finish(null, "connection_timeout");
1761
+ }
1762
+ }, timeout);
1763
+ if (firstStep !== SMTPStep.greeting) {
1764
+ executeStep(firstStep);
1765
+ }
1766
+ socket.setTimeout(timeout, () => {
1234
1767
  if (!resolved) {
1235
- log("[verifyMailboxSMTP] close socket", err);
1236
- ret(null);
1768
+ log(`Socket timeout after ${timeout}ms`);
1769
+ finish(null, "socket_timeout");
1237
1770
  }
1238
1771
  });
1239
- socket.on("timeout", () => {
1240
- log("[verifyMailboxSMTP] timeout socket");
1241
- ret(null);
1772
+ socket.on("error", (error) => {
1773
+ log(`Socket error: ${error.message}`);
1774
+ if (!resolved)
1775
+ finish(null, "connection_error");
1242
1776
  });
1243
- resTimeout = setTimeout(() => {
1244
- log(`[verifyMailboxSMTP] timed out (${timeout} ms)`);
1777
+ socket.on("close", () => {
1245
1778
  if (!resolved) {
1246
- socket.destroy();
1247
- ret(null);
1779
+ log("Socket closed unexpectedly");
1780
+ finish(null, "connection_closed");
1248
1781
  }
1249
- }, timeout);
1782
+ });
1783
+ const originalHandleData = handleData;
1784
+ handleData = (data) => {
1785
+ resetActivityTimeout();
1786
+ originalHandleData(data);
1787
+ };
1788
+ const enhancedCleanup = () => {
1789
+ if (resolved)
1790
+ return;
1791
+ resolved = true;
1792
+ if (connectionTimeout)
1793
+ clearTimeout(connectionTimeout);
1794
+ if (stepTimeout)
1795
+ clearTimeout(stepTimeout);
1796
+ socket.setTimeout(0);
1797
+ try {
1798
+ socket === null || socket === void 0 ? void 0 : socket.write("QUIT\r\n");
1799
+ } catch {
1800
+ }
1801
+ setTimeout(() => socket === null || socket === void 0 ? void 0 : socket.destroy(), 100);
1802
+ };
1803
+ cleanup = enhancedCleanup;
1250
1804
  });
1251
1805
  }
1252
1806
 
1253
- var VerificationErrorCode;
1254
- (function(VerificationErrorCode2) {
1255
- VerificationErrorCode2["INVALID_FORMAT"] = "INVALID_FORMAT";
1256
- VerificationErrorCode2["INVALID_DOMAIN"] = "INVALID_DOMAIN";
1257
- VerificationErrorCode2["NO_MX_RECORDS"] = "NO_MX_RECORDS";
1258
- VerificationErrorCode2["SMTP_CONNECTION_FAILED"] = "SMTP_CONNECTION_FAILED";
1259
- VerificationErrorCode2["SMTP_TIMEOUT"] = "SMTP_TIMEOUT";
1260
- VerificationErrorCode2["MAILBOX_NOT_FOUND"] = "MAILBOX_NOT_FOUND";
1261
- VerificationErrorCode2["MAILBOX_FULL"] = "MAILBOX_FULL";
1262
- VerificationErrorCode2["NETWORK_ERROR"] = "NETWORK_ERROR";
1263
- VerificationErrorCode2["DISPOSABLE_EMAIL"] = "DISPOSABLE_EMAIL";
1264
- VerificationErrorCode2["FREE_EMAIL_PROVIDER"] = "FREE_EMAIL_PROVIDER";
1265
- })(VerificationErrorCode || (VerificationErrorCode = {}));
1266
-
1267
- async function isValidEmailDomain(emailOrDomain, cache) {
1268
- let [_, emailDomain] = (emailOrDomain === null || emailOrDomain === void 0 ? void 0 : emailOrDomain.split("@")) || [];
1269
- if (!emailDomain) {
1270
- emailDomain = _;
1271
- }
1272
- if (!emailDomain) {
1273
- return false;
1274
- }
1275
- const cacheStore = getCacheStore(cache, "domainValid");
1276
- const cached = await cacheStore.get(emailDomain);
1277
- if (cached !== null && cached !== void 0) {
1278
- return cached;
1279
- }
1280
- try {
1281
- const result = isValid(emailDomain) || false;
1282
- await cacheStore.set(emailDomain, result);
1283
- return result;
1284
- } catch (_e) {
1285
- await cacheStore.set(emailDomain, false);
1286
- return false;
1287
- }
1288
- }
1289
- function isValidEmail(emailAddress) {
1290
- if (!emailAddress || typeof emailAddress !== "string") {
1291
- return false;
1292
- }
1293
- const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
1294
- const emailLower = emailAddress.toLowerCase();
1295
- if (emailLower.indexOf(".+") !== -1)
1296
- return false;
1297
- if (emailLower.indexOf("..") !== -1)
1298
- return false;
1299
- if (emailLower.startsWith(".") || emailLower.endsWith("."))
1300
- return false;
1301
- const parts = emailAddress.split("@");
1302
- if (parts.length !== 2)
1303
- return false;
1304
- const [localPart, domain] = parts;
1305
- if (!localPart || !domain)
1306
- return false;
1307
- if (localPart.length > 64)
1308
- return false;
1309
- if (domain.length > 253)
1310
- return false;
1311
- return re.test(emailLower);
1312
- }
1313
-
1314
1807
  const defaultRegex = {
1315
1808
  domainName: "Domain Name: *([^\\s]+)",
1316
1809
  registrar: "Registrar: *(.+)",
@@ -1728,7 +2221,7 @@ function parseWhoisData({ rawData, domain }) {
1728
2221
  return result;
1729
2222
  }
1730
2223
 
1731
- const WHOIS_SERVERS = {
2224
+ const whoisServers = {
1732
2225
  com: "whois.verisign-grs.com",
1733
2226
  net: "whois.verisign-grs.com",
1734
2227
  org: "whois.pir.org",
@@ -1828,7 +2321,7 @@ async function getWhoisData(domain, timeout = 5e3, debug = false, cache) {
1828
2321
  throw new Error("Invalid domain");
1829
2322
  }
1830
2323
  log(`[whois] extracted TLD: ${tld} for domain: ${domain}`);
1831
- const whoisServer = WHOIS_SERVERS[tld];
2324
+ const whoisServer = whoisServers[tld];
1832
2325
  if (!whoisServer) {
1833
2326
  log(`[whois] no specific server for TLD ${tld}, trying IANA`);
1834
2327
  const defaultServer = "whois.iana.org";
@@ -1854,8 +2347,8 @@ async function getWhoisData(domain, timeout = 5e3, debug = false, cache) {
1854
2347
  await cacheStore.set(cacheKey, whoisData);
1855
2348
  log(`[whois] successfully retrieved and cached WHOIS data for ${domain}`);
1856
2349
  return whoisData;
1857
- } catch (_error) {
1858
- log(`[whois] failed to get WHOIS data for ${domain}: ${_error instanceof Error ? _error.message : "Unknown error"}`);
2350
+ } catch (ignoredError) {
2351
+ log(`[whois] failed to get WHOIS data for ${domain}: ${ignoredError instanceof Error ? ignoredError.message : "Unknown error"}`);
1859
2352
  return null;
1860
2353
  }
1861
2354
  }
@@ -1892,8 +2385,8 @@ async function getDomainAge(domain, timeout = 5e3, debug = false, cache) {
1892
2385
  expirationDate: whoisData.expirationDate ? new Date(whoisData.expirationDate) : null,
1893
2386
  updatedDate: whoisData.updatedDate ? new Date(whoisData.updatedDate) : null
1894
2387
  };
1895
- } catch (_error) {
1896
- log(`[whois] error getting domain age for ${domain}: ${_error instanceof Error ? _error.message : "Unknown error"}`);
2388
+ } catch (ignoredError) {
2389
+ log(`[whois] error getting domain age for ${domain}: ${ignoredError instanceof Error ? ignoredError.message : "Unknown error"}`);
1897
2390
  return null;
1898
2391
  }
1899
2392
  }
@@ -1964,8 +2457,8 @@ async function getDomainRegistrationStatus(domain, timeout = 5e3, debug = false,
1964
2457
  isPendingDelete,
1965
2458
  isLocked
1966
2459
  };
1967
- } catch (_error) {
1968
- log(`[whois] error getting domain registration status for ${domain}: ${_error instanceof Error ? _error.message : "Unknown error"}`);
2460
+ } catch (ignoredError) {
2461
+ log(`[whois] error getting domain registration status for ${domain}: ${ignoredError instanceof Error ? ignoredError.message : "Unknown error"}`);
1969
2462
  return null;
1970
2463
  }
1971
2464
  }
@@ -2141,7 +2634,7 @@ function createErrorResult(email, _error) {
2141
2634
  metadata: {
2142
2635
  verificationTime: 0,
2143
2636
  cached: false,
2144
- error: VerificationErrorCode.SMTP_CONNECTION_FAILED
2637
+ error: VerificationErrorCode.smtpConnectionFailed
2145
2638
  }
2146
2639
  };
2147
2640
  }
@@ -2161,25 +2654,31 @@ async function isDisposableEmail(params) {
2161
2654
  let cached;
2162
2655
  try {
2163
2656
  cached = await cacheStore.get(emailDomain);
2164
- } catch (_error) {
2657
+ } catch (ignoredError) {
2165
2658
  cached = null;
2166
2659
  }
2167
2660
  if (cached !== null && cached !== void 0) {
2168
- log(`[isDisposableEmail] Cache hit for ${emailDomain}: ${cached}`);
2169
- return cached;
2661
+ log(`[isDisposableEmail] Cache hit for ${emailDomain}: ${cached.isDisposable}`);
2662
+ return cached.isDisposable;
2170
2663
  }
2171
2664
  if (!disposableEmailProviders) {
2172
2665
  disposableEmailProviders = new Set(require("./disposable-email-providers.json"));
2173
2666
  }
2174
- const result = disposableEmailProviders.has(emailDomain);
2667
+ const isDisposable = disposableEmailProviders.has(emailDomain);
2668
+ const richResult = {
2669
+ isDisposable,
2670
+ source: "disposable-email-providers.json",
2671
+ category: isDisposable ? "disposable" : void 0,
2672
+ checkedAt: Date.now()
2673
+ };
2175
2674
  try {
2176
- await cacheStore.set(emailDomain, result);
2177
- log(`[isDisposableEmail] Cached result for ${emailDomain}: ${result}`);
2178
- } catch (_error) {
2675
+ await cacheStore.set(emailDomain, richResult);
2676
+ log(`[isDisposableEmail] Cached result for ${emailDomain}: ${isDisposable}`);
2677
+ } catch (ignoredError) {
2179
2678
  log(`[isDisposableEmail] Cache write error for ${emailDomain}`);
2180
2679
  }
2181
- log(`[isDisposableEmail] Check result for ${emailDomain}: ${result}`);
2182
- return result;
2680
+ log(`[isDisposableEmail] Check result for ${emailDomain}: ${isDisposable}`);
2681
+ return isDisposable;
2183
2682
  }
2184
2683
  async function isFreeEmail(params) {
2185
2684
  const { emailOrDomain, cache, logger } = params;
@@ -2194,25 +2693,30 @@ async function isFreeEmail(params) {
2194
2693
  let cached;
2195
2694
  try {
2196
2695
  cached = await cacheStore.get(emailDomain);
2197
- } catch (_error) {
2696
+ } catch (ignoredError) {
2198
2697
  cached = null;
2199
2698
  }
2200
2699
  if (cached !== null && cached !== void 0) {
2201
- log(`[isFreeEmail] Cache hit for ${emailDomain}: ${cached}`);
2202
- return cached;
2700
+ log(`[isFreeEmail] Cache hit for ${emailDomain}: ${cached.isFree}`);
2701
+ return cached.isFree;
2203
2702
  }
2204
2703
  if (!freeEmailProviders) {
2205
2704
  freeEmailProviders = new Set(require("./free-email-providers.json"));
2206
2705
  }
2207
- const result = freeEmailProviders.has(emailDomain);
2706
+ const isFree = freeEmailProviders.has(emailDomain);
2707
+ const richResult = {
2708
+ isFree,
2709
+ provider: isFree ? emailDomain : void 0,
2710
+ checkedAt: Date.now()
2711
+ };
2208
2712
  try {
2209
- await cacheStore.set(emailDomain, result);
2210
- log(`[isFreeEmail] Cached result for ${emailDomain}: ${result}`);
2211
- } catch (_error) {
2713
+ await cacheStore.set(emailDomain, richResult);
2714
+ log(`[isFreeEmail] Cached result for ${emailDomain}: ${isFree}`);
2715
+ } catch (ignoredError) {
2212
2716
  log(`[isFreeEmail] Cache write error for ${emailDomain}`);
2213
2717
  }
2214
- log(`[isFreeEmail] Check result for ${emailDomain}: ${result}`);
2215
- return result;
2718
+ log(`[isFreeEmail] Check result for ${emailDomain}: ${isFree}`);
2719
+ return isFree;
2216
2720
  }
2217
2721
  const domainPorts = {
2218
2722
  // 465 or 587
@@ -2220,7 +2724,7 @@ const domainPorts = {
2220
2724
  "ovh.net": 465
2221
2725
  };
2222
2726
  async function verifyEmail(params) {
2223
- var _a;
2727
+ var _a, _b;
2224
2728
  const { emailAddress, timeout = 4e3, verifyMx = true, verifySmtp = false, debug = false, checkDisposable = true, checkFree = true, detectName: detectName2 = false, nameDetectionMethod, suggestDomain: suggestDomain2 = true, domainSuggestionMethod, commonDomains, checkDomainAge = false, checkDomainRegistration = false, whoisTimeout = 5e3, skipMxForDisposable = false, skipDomainWhoisForDisposable = false } = params;
2225
2729
  const startTime = Date.now();
2226
2730
  const log = debug ? console.debug : (..._args) => {
@@ -2240,7 +2744,7 @@ async function verifyEmail(params) {
2240
2744
  if (!isValidEmail(emailAddress)) {
2241
2745
  if (result.metadata) {
2242
2746
  result.metadata.verificationTime = Date.now() - startTime;
2243
- result.metadata.error = VerificationErrorCode.INVALID_FORMAT;
2747
+ result.metadata.error = VerificationErrorCode.invalidFormat;
2244
2748
  }
2245
2749
  return result;
2246
2750
  }
@@ -2266,14 +2770,14 @@ async function verifyEmail(params) {
2266
2770
  if (!domain || !local) {
2267
2771
  if (result.metadata) {
2268
2772
  result.metadata.verificationTime = Date.now() - startTime;
2269
- result.metadata.error = VerificationErrorCode.INVALID_FORMAT;
2773
+ result.metadata.error = VerificationErrorCode.invalidFormat;
2270
2774
  }
2271
2775
  return result;
2272
2776
  }
2273
2777
  if (!await isValidEmailDomain(domain, params.cache)) {
2274
2778
  if (result.metadata) {
2275
2779
  result.metadata.verificationTime = Date.now() - startTime;
2276
- result.metadata.error = VerificationErrorCode.INVALID_DOMAIN;
2780
+ result.metadata.error = VerificationErrorCode.invalidDomain;
2277
2781
  }
2278
2782
  return result;
2279
2783
  }
@@ -2282,7 +2786,7 @@ async function verifyEmail(params) {
2282
2786
  result.isDisposable = await isDisposableEmail({ emailOrDomain: emailAddress, cache: params.cache, logger: log });
2283
2787
  log(`[verifyEmail] Disposable check result: ${result.isDisposable}`);
2284
2788
  if (result.isDisposable && result.metadata) {
2285
- result.metadata.error = VerificationErrorCode.DISPOSABLE_EMAIL;
2789
+ result.metadata.error = VerificationErrorCode.disposableEmail;
2286
2790
  }
2287
2791
  }
2288
2792
  if (checkFree) {
@@ -2303,8 +2807,8 @@ async function verifyEmail(params) {
2303
2807
  try {
2304
2808
  result.domainAge = await getDomainAge(domain, whoisTimeout, debug, params.cache);
2305
2809
  log(`[verifyEmail] Domain age result:`, result.domainAge ? `${result.domainAge.ageInDays} days` : "null");
2306
- } catch (err) {
2307
- log("[verifyEmail] Failed to get domain age", err);
2810
+ } catch (error) {
2811
+ log("[verifyEmail] Failed to get domain age", error);
2308
2812
  result.domainAge = null;
2309
2813
  }
2310
2814
  } else if (checkDomainAge && shouldSkipDomainWhois) {
@@ -2315,8 +2819,8 @@ async function verifyEmail(params) {
2315
2819
  try {
2316
2820
  result.domainRegistration = await getDomainRegistrationStatus(domain, whoisTimeout, debug, params.cache);
2317
2821
  log(`[verifyEmail] Domain registration result:`, ((_a = result.domainRegistration) === null || _a === void 0 ? void 0 : _a.isRegistered) ? "registered" : "not registered");
2318
- } catch (err) {
2319
- log("[verifyEmail] Failed to get domain registration status", err);
2822
+ } catch (error) {
2823
+ log("[verifyEmail] Failed to get domain registration status", error);
2320
2824
  result.domainRegistration = null;
2321
2825
  }
2322
2826
  } else if (checkDomainRegistration && shouldSkipDomainWhois) {
@@ -2329,14 +2833,14 @@ async function verifyEmail(params) {
2329
2833
  result.validMx = mxRecords.length > 0;
2330
2834
  log(`[verifyEmail] MX records found: ${mxRecords.length}, valid: ${result.validMx}`);
2331
2835
  if (!result.validMx && result.metadata) {
2332
- result.metadata.error = VerificationErrorCode.NO_MX_RECORDS;
2836
+ result.metadata.error = VerificationErrorCode.noMxRecords;
2333
2837
  }
2334
2838
  if (verifySmtp && mxRecords.length > 0) {
2335
2839
  const cacheKey = `${emailAddress}:smtp`;
2336
2840
  const smtpCacheInstance = getCacheStore(params.cache, "smtp");
2337
2841
  const cachedSmtp = await smtpCacheInstance.get(cacheKey);
2338
2842
  if (cachedSmtp !== null && cachedSmtp !== void 0) {
2339
- result.validSmtp = cachedSmtp;
2843
+ result.validSmtp = (_b = cachedSmtp.isDeliverable) !== null && _b !== void 0 ? _b : null;
2340
2844
  log(`[verifyEmail] SMTP result from cache: ${result.validSmtp} for ${emailAddress}`);
2341
2845
  if (result.metadata) {
2342
2846
  result.metadata.cached = true;
@@ -2356,30 +2860,39 @@ async function verifyEmail(params) {
2356
2860
  domainPort = domainPorts[mxDomain.domain];
2357
2861
  }
2358
2862
  }
2359
- const smtpResult = await verifyMailboxSMTP({
2863
+ const { smtpResult, cached, port } = await verifyMailboxSMTP({
2360
2864
  local,
2361
2865
  domain,
2362
2866
  mxRecords,
2363
- timeout,
2364
- debug,
2365
- port: domainPort,
2366
- retryAttempts: params.retryAttempts
2867
+ options: {
2868
+ cache: params.cache,
2869
+ ports: domainPort ? [domainPort] : void 0,
2870
+ timeout,
2871
+ debug,
2872
+ maxRetries: params.retryAttempts
2873
+ }
2367
2874
  });
2368
2875
  await smtpCacheInstance.set(cacheKey, smtpResult);
2369
- result.validSmtp = smtpResult;
2876
+ if (!smtpResult.canConnectSmtp) {
2877
+ result.validSmtp = null;
2878
+ } else {
2879
+ result.validSmtp = smtpResult.isDeliverable;
2880
+ }
2881
+ if (result.metadata)
2882
+ result.metadata.cached = cached;
2370
2883
  log(`[verifyEmail] SMTP verification result: ${result.validSmtp} for ${emailAddress} (cached for future use)`);
2371
2884
  }
2372
2885
  if (result.validSmtp === false && result.metadata) {
2373
- result.metadata.error = VerificationErrorCode.MAILBOX_NOT_FOUND;
2886
+ result.metadata.error = VerificationErrorCode.mailboxNotFound;
2374
2887
  } else if (result.validSmtp === null && result.metadata) {
2375
- result.metadata.error = VerificationErrorCode.SMTP_CONNECTION_FAILED;
2888
+ result.metadata.error = VerificationErrorCode.smtpConnectionFailed;
2376
2889
  }
2377
2890
  }
2378
- } catch (err) {
2379
- log("[verifyEmail] Failed to resolve MX records", err);
2891
+ } catch (error) {
2892
+ log("[verifyEmail] Failed to resolve MX records", error);
2380
2893
  result.validMx = false;
2381
2894
  if (result.metadata) {
2382
- result.metadata.error = VerificationErrorCode.NO_MX_RECORDS;
2895
+ result.metadata.error = VerificationErrorCode.noMxRecords;
2383
2896
  }
2384
2897
  }
2385
2898
  } else if ((verifyMx || verifySmtp) && shouldSkipMx) {
@@ -2391,5 +2904,5 @@ async function verifyEmail(params) {
2391
2904
  return result;
2392
2905
  }
2393
2906
 
2394
- export { COMMON_EMAIL_DOMAINS, DEFAULT_CACHE_OPTIONS, LRUAdapter, RedisAdapter, VerificationErrorCode, cleanNameForAlgrothin, clearDefaultCache, defaultDomainSuggestionMethod, defaultNameDetectionMethod, detectName, detectNameForAlgrothin, detectNameFromEmail, domainPorts, getCacheStore, getDefaultCache, getDomainAge, getDomainRegistrationStatus, getDomainSimilarity, isCommonDomain, isDisposableEmail, isFreeEmail, isValidEmail, isValidEmailDomain, resetDefaultCache, suggestDomain, suggestEmailDomain, verifyEmail, verifyEmailBatch };
2907
+ export { DEFAULT_CACHE_OPTIONS, EmailProvider, LRUAdapter, RedisAdapter, SMTPStep, VerificationErrorCode, cleanNameForAlgorithm, clearDefaultCache, commonEmailDomains, defaultDomainSuggestionMethod, defaultNameDetectionMethod, detectName, detectNameForAlgorithm, detectNameFromEmail, domainPorts, getCacheStore, getDefaultCache, getDomainAge, getDomainRegistrationStatus, getDomainSimilarity, isCommonDomain, isDisposableEmail, isFreeEmail, isValidEmail, isValidEmailDomain, parseSmtpError, resetDefaultCache, suggestDomain, suggestEmailDomain, verifyEmail, verifyEmailBatch };
2395
2908
  //# sourceMappingURL=index.esm.js.map