@emailcheck/email-validator-js 4.0.0-beta.1 → 4.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export { isFreeEmail } from './is-free-email';
18
18
  export { isSpamEmail } from './is-spam-email';
19
19
  export { isSpamName } from './is-spam-name';
20
20
  export { cleanNameForAlgorithm, defaultNameDetectionMethod, detectName, detectNameForAlgorithm, detectNameFromEmail, } from './name-detector';
21
+ export { refineReasonByEnhancedStatus } from './refine-reason';
21
22
  export { type ParsedSmtpError, parseSmtpError } from './smtp-error-parser';
22
23
  export { ArrayTranscriptCollector, NULL_COLLECTOR, type TranscriptCollector, } from './transcript';
23
24
  export * from './types';
package/dist/index.esm.js CHANGED
@@ -155,10 +155,8 @@ var VerificationErrorCode;
155
155
  VerificationErrorCode2["smtpConnectionFailed"] = "SMTP_CONNECTION_FAILED";
156
156
  VerificationErrorCode2["smtpTimeout"] = "SMTP_TIMEOUT";
157
157
  VerificationErrorCode2["mailboxNotFound"] = "MAILBOX_NOT_FOUND";
158
- VerificationErrorCode2["mailboxFull"] = "MAILBOX_FULL";
159
158
  VerificationErrorCode2["networkError"] = "NETWORK_ERROR";
160
159
  VerificationErrorCode2["disposableEmail"] = "DISPOSABLE_EMAIL";
161
- VerificationErrorCode2["freeEmailProvider"] = "FREE_EMAIL_PROVIDER";
162
160
  })(VerificationErrorCode || (VerificationErrorCode = {}));
163
161
  var EmailProvider;
164
162
  (function(EmailProvider2) {
@@ -175,6 +173,7 @@ var SMTPStep;
175
173
  SMTPStep2["greeting"] = "GREETING";
176
174
  SMTPStep2["ehlo"] = "EHLO";
177
175
  SMTPStep2["helo"] = "HELO";
176
+ SMTPStep2["startTls"] = "STARTTLS";
178
177
  SMTPStep2["mailFrom"] = "MAIL_FROM";
179
178
  SMTPStep2["rcptTo"] = "RCPT_TO";
180
179
  })(SMTPStep || (SMTPStep = {}));
@@ -993,7 +992,7 @@ function defaultProbeLocal() {
993
992
  return `${randomBytes(8).toString("hex")}-noexist`;
994
993
  }
995
994
  async function verifyMailboxSMTP(params) {
996
- var _a, _b, _c, _d, _e, _f, _g, _h;
995
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
997
996
  const { local, domain, options = {} } = params;
998
997
  const mxRecords = (_a = params.mxRecords) !== null && _a !== void 0 ? _a : [];
999
998
  const ports = ((_b = options.ports) !== null && _b !== void 0 ? _b : DEFAULT_PORTS).filter((port) => Number.isInteger(port) && port > 0 && port < 65536);
@@ -1025,7 +1024,8 @@ async function verifyMailboxSMTP(params) {
1025
1024
  sequence,
1026
1025
  log,
1027
1026
  catchAllProbeLocal: options.catchAllProbeLocal,
1028
- pipelining: (_h = options.pipelining) !== null && _h !== void 0 ? _h : "auto"
1027
+ pipelining: (_h = options.pipelining) !== null && _h !== void 0 ? _h : "auto",
1028
+ startTls: (_j = options.startTls) !== null && _j !== void 0 ? _j : "auto"
1029
1029
  };
1030
1030
  const verdictCache = cache ? getCacheStore(cache, "smtp") : null;
1031
1031
  const verdictKey = `${primaryMx}:${local}@${domain}`;
@@ -1159,9 +1159,12 @@ class SMTPProbeConnection {
1159
1159
  this.buffer = "";
1160
1160
  this.resolved = false;
1161
1161
  this.currentStepIndex = 0;
1162
+ this.tlsUpgrading = false;
1163
+ this.postUpgradeReEhlo = false;
1162
1164
  this.transcript = [];
1163
1165
  this.commands = [];
1164
1166
  this.supportsPipelining = false;
1167
+ this.supportsStartTls = false;
1165
1168
  this.dualPhase = "idle";
1166
1169
  this.realOutcome = "pending";
1167
1170
  this.probeOutcome = "pending";
@@ -1179,7 +1182,7 @@ class SMTPProbeConnection {
1179
1182
  this.processLine(line);
1180
1183
  }
1181
1184
  };
1182
- const defaultSteps = [SMTPStep.greeting, SMTPStep.ehlo, SMTPStep.mailFrom, SMTPStep.rcptTo];
1185
+ const defaultSteps = [SMTPStep.greeting, SMTPStep.ehlo, SMTPStep.startTls, SMTPStep.mailFrom, SMTPStep.rcptTo];
1183
1186
  this.steps = [...(_b = (_a = p.sequence) === null || _a === void 0 ? void 0 : _a.steps) !== null && _b !== void 0 ? _b : defaultSteps];
1184
1187
  this.isTLS = PORT_TLS[p.port] === true;
1185
1188
  const servername = isIPAddress(p.mxHost) ? void 0 : p.mxHost;
@@ -1199,7 +1202,8 @@ class SMTPProbeConnection {
1199
1202
  this.connect();
1200
1203
  this.armConnectionTimer();
1201
1204
  } catch (error) {
1202
- this.finish(null, `connect_throw:${error instanceof Error ? error.message : "unknown"}`);
1205
+ this.p.log(`connect threw: ${error instanceof Error ? error.message : "unknown"}`);
1206
+ this.finish(null, "connection_error");
1203
1207
  }
1204
1208
  });
1205
1209
  }
@@ -1258,6 +1262,9 @@ class SMTPProbeConnection {
1258
1262
  case SMTPStep.helo:
1259
1263
  this.send(`HELO ${this.p.hostname}`);
1260
1264
  return;
1265
+ case SMTPStep.startTls:
1266
+ this.executeStartTls();
1267
+ return;
1261
1268
  case SMTPStep.mailFrom: {
1262
1269
  const from = (_b = (_a = this.p.sequence) === null || _a === void 0 ? void 0 : _a.from) !== null && _b !== void 0 ? _b : `<${this.p.local}@${this.p.domain}>`;
1263
1270
  this.send(`MAIL FROM:${from}`);
@@ -1268,6 +1275,73 @@ class SMTPProbeConnection {
1268
1275
  return;
1269
1276
  }
1270
1277
  }
1278
+ /**
1279
+ * Conditional STARTTLS upgrade. Skipped (advances to next step) when:
1280
+ * - already TLS (implicit-TLS port like 465 or already-upgraded)
1281
+ * - `startTls === 'never'`
1282
+ * - `startTls === 'auto'` AND the MX didn't advertise STARTTLS in EHLO
1283
+ *
1284
+ * Sends `STARTTLS` and waits for 220 when:
1285
+ * - `startTls === 'force'` (regardless of advertisement)
1286
+ * - `startTls === 'auto'` AND the MX advertised it
1287
+ *
1288
+ * On 220, `tls.connect()` wraps the existing socket. After the handshake
1289
+ * we re-EHLO (mandatory per RFC 3207 §4.2 — pre-TLS state must be
1290
+ * discarded) before continuing to MAIL FROM.
1291
+ */
1292
+ executeStartTls() {
1293
+ const mode = this.p.startTls;
1294
+ const wantsUpgrade = !this.isTLS && mode !== "never" && (mode === "force" || mode === "auto" && this.supportsStartTls);
1295
+ if (!wantsUpgrade) {
1296
+ this.nextStep();
1297
+ return;
1298
+ }
1299
+ this.send("STARTTLS");
1300
+ }
1301
+ /**
1302
+ * Wrap the plaintext socket with TLS in place. Called after the server
1303
+ * answers our STARTTLS with 220. Detaches the plaintext-socket listeners
1304
+ * (TLS owns the underlying transport now), re-installs them on the wrapped
1305
+ * socket, resets EHLO-derived capabilities, and re-issues EHLO once the
1306
+ * handshake completes (RFC 3207 §4.2 mandates re-EHLO after upgrade —
1307
+ * pre-TLS state must be discarded).
1308
+ */
1309
+ upgradeToTls() {
1310
+ const plainSocket = this.socket;
1311
+ if (!plainSocket) {
1312
+ this.finish(null, "tls_upgrade_failed");
1313
+ return;
1314
+ }
1315
+ const detach = plainSocket.removeAllListeners;
1316
+ if (typeof detach === "function") {
1317
+ detach.call(plainSocket, "data");
1318
+ detach.call(plainSocket, "error");
1319
+ detach.call(plainSocket, "close");
1320
+ detach.call(plainSocket, "timeout");
1321
+ }
1322
+ try {
1323
+ plainSocket.setTimeout(0);
1324
+ } catch {
1325
+ }
1326
+ this.tlsUpgrading = true;
1327
+ const servername = isIPAddress(this.p.mxHost) ? void 0 : this.p.mxHost;
1328
+ const tlsSocket = tls.connect({ ...this.tlsOptions, socket: plainSocket, servername }, () => {
1329
+ this.tlsUpgrading = false;
1330
+ this.isTLS = true;
1331
+ this.buffer = "";
1332
+ this.supportsStartTls = false;
1333
+ this.supportsPipelining = false;
1334
+ this.postUpgradeReEhlo = true;
1335
+ this.send(`EHLO ${this.p.hostname}`);
1336
+ });
1337
+ this.socket = tlsSocket;
1338
+ this.socket.on("data", this.onData);
1339
+ this.socket.on("error", () => {
1340
+ this.finish(null, this.tlsUpgrading ? "tls_handshake_failed" : "connection_error");
1341
+ });
1342
+ this.socket.on("close", () => this.finish(null, "connection_closed"));
1343
+ this.socket.setTimeout(this.p.timeout, () => this.finish(null, "socket_timeout"));
1344
+ }
1271
1345
  /**
1272
1346
  * Send the dual-probe envelope (real RCPT + probe RCPT + RSET). Pipelined
1273
1347
  * when the MX advertised PIPELINING (or `pipelining: 'force'`); sequential
@@ -1326,10 +1400,13 @@ ${rsetCmd}\r
1326
1400
  }
1327
1401
  if (MULTILINE_RE.test(line)) {
1328
1402
  const step = this.steps[this.currentStepIndex];
1329
- if ((step === SMTPStep.ehlo || step === SMTPStep.helo) && line.startsWith("250-")) {
1403
+ const isEhloLike = step === SMTPStep.ehlo || step === SMTPStep.helo || step === SMTPStep.startTls && this.postUpgradeReEhlo;
1404
+ if (isEhloLike && line.startsWith("250-")) {
1330
1405
  const upper = line.toUpperCase();
1331
1406
  if (upper.includes("PIPELINING"))
1332
1407
  this.supportsPipelining = true;
1408
+ if (upper.includes("STARTTLS"))
1409
+ this.supportsStartTls = true;
1333
1410
  }
1334
1411
  return;
1335
1412
  }
@@ -1364,6 +1441,21 @@ ${rsetCmd}\r
1364
1441
  else
1365
1442
  this.finish(null, "helo_failed");
1366
1443
  return;
1444
+ case SMTPStep.startTls:
1445
+ if (this.postUpgradeReEhlo) {
1446
+ this.postUpgradeReEhlo = false;
1447
+ if (code === 250)
1448
+ this.nextStep();
1449
+ else
1450
+ this.finish(null, "ehlo_failed");
1451
+ return;
1452
+ }
1453
+ if (code === 220) {
1454
+ this.upgradeToTls();
1455
+ } else {
1456
+ this.finish(null, "tls_upgrade_failed");
1457
+ }
1458
+ return;
1367
1459
  case SMTPStep.mailFrom:
1368
1460
  if (code === 250)
1369
1461
  this.nextStep();
@@ -2525,6 +2617,36 @@ function isSpamName(name) {
2525
2617
  return true;
2526
2618
  }
2527
2619
 
2620
+ const REFINEMENT_TABLE = {
2621
+ // X.1.x — addressing
2622
+ "5.1.1": "mailbox_does_not_exist",
2623
+ "5.1.2": "bad_destination_system",
2624
+ "5.1.3": "bad_destination_address",
2625
+ "5.1.6": "mailbox_moved",
2626
+ "5.1.10": "recipient_address_has_null_mx",
2627
+ // X.2.x — mailbox status
2628
+ "5.2.0": "mailbox_status_other",
2629
+ "5.2.1": "mailbox_disabled",
2630
+ "5.2.2": "mailbox_full",
2631
+ "5.2.3": "message_too_long",
2632
+ "5.2.4": "mailing_list_expansion_problem",
2633
+ // X.4.x — network / routing
2634
+ "4.4.1": "no_answer_from_host",
2635
+ "4.4.2": "bad_connection",
2636
+ // X.7.x — security / policy
2637
+ "5.7.0": "security_other",
2638
+ "5.7.1": "delivery_not_authorized",
2639
+ "5.7.25": "no_reverse_dns",
2640
+ "5.7.26": "multiple_authentication_failures"
2641
+ };
2642
+ function refineReasonByEnhancedStatus(reason, enhancedStatus) {
2643
+ var _a;
2644
+ const base = reason !== null && reason !== void 0 ? reason : "unknown";
2645
+ if (!enhancedStatus)
2646
+ return base;
2647
+ return (_a = REFINEMENT_TABLE[enhancedStatus]) !== null && _a !== void 0 ? _a : base;
2648
+ }
2649
+
2528
2650
  const NETWORK_ERROR_PATTERNS = [
2529
2651
  "etimedout",
2530
2652
  "econnrefused",
@@ -2628,5 +2750,5 @@ function parseSmtpError(errorMessage) {
2628
2750
  return { isDisabled, hasFullInbox, isCatchAll, isInvalid };
2629
2751
  }
2630
2752
 
2631
- export { ArrayTranscriptCollector, DEFAULT_CACHE_OPTIONS, EmailProvider, LRUAdapter, NULL_COLLECTOR, RedisAdapter, SMTPStep, VerificationErrorCode, cleanNameForAlgorithm, clearDefaultCache, commonEmailDomains, defaultDomainSuggestionMethod, defaultNameDetectionMethod, detectName, detectNameForAlgorithm, detectNameFromEmail, domainPorts, getCacheStore, getDefaultCache, getDomainAge, getDomainRegistrationStatus, getDomainSimilarity, isCommonDomain, isDisposableEmail, isFreeEmail, isSpamEmail, isSpamName, isValidEmail, isValidEmailDomain, parseSmtpError, suggestDomain, suggestEmailDomain, verifyEmail, verifyEmailBatch };
2753
+ export { ArrayTranscriptCollector, DEFAULT_CACHE_OPTIONS, EmailProvider, LRUAdapter, NULL_COLLECTOR, RedisAdapter, SMTPStep, VerificationErrorCode, cleanNameForAlgorithm, clearDefaultCache, commonEmailDomains, defaultDomainSuggestionMethod, defaultNameDetectionMethod, detectName, detectNameForAlgorithm, detectNameFromEmail, domainPorts, getCacheStore, getDefaultCache, getDomainAge, getDomainRegistrationStatus, getDomainSimilarity, isCommonDomain, isDisposableEmail, isFreeEmail, isSpamEmail, isSpamName, isValidEmail, isValidEmailDomain, parseSmtpError, refineReasonByEnhancedStatus, suggestDomain, suggestEmailDomain, verifyEmail, verifyEmailBatch };
2632
2754
  //# sourceMappingURL=index.esm.js.map