@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.js CHANGED
@@ -177,10 +177,8 @@ exports.VerificationErrorCode = void 0;
177
177
  VerificationErrorCode2["smtpConnectionFailed"] = "SMTP_CONNECTION_FAILED";
178
178
  VerificationErrorCode2["smtpTimeout"] = "SMTP_TIMEOUT";
179
179
  VerificationErrorCode2["mailboxNotFound"] = "MAILBOX_NOT_FOUND";
180
- VerificationErrorCode2["mailboxFull"] = "MAILBOX_FULL";
181
180
  VerificationErrorCode2["networkError"] = "NETWORK_ERROR";
182
181
  VerificationErrorCode2["disposableEmail"] = "DISPOSABLE_EMAIL";
183
- VerificationErrorCode2["freeEmailProvider"] = "FREE_EMAIL_PROVIDER";
184
182
  })(exports.VerificationErrorCode || (exports.VerificationErrorCode = {}));
185
183
  exports.EmailProvider = void 0;
186
184
  (function(EmailProvider2) {
@@ -197,6 +195,7 @@ exports.SMTPStep = void 0;
197
195
  SMTPStep2["greeting"] = "GREETING";
198
196
  SMTPStep2["ehlo"] = "EHLO";
199
197
  SMTPStep2["helo"] = "HELO";
198
+ SMTPStep2["startTls"] = "STARTTLS";
200
199
  SMTPStep2["mailFrom"] = "MAIL_FROM";
201
200
  SMTPStep2["rcptTo"] = "RCPT_TO";
202
201
  })(exports.SMTPStep || (exports.SMTPStep = {}));
@@ -1015,7 +1014,7 @@ function defaultProbeLocal() {
1015
1014
  return `${node_crypto.randomBytes(8).toString("hex")}-noexist`;
1016
1015
  }
1017
1016
  async function verifyMailboxSMTP(params) {
1018
- var _a, _b, _c, _d, _e, _f, _g, _h;
1017
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1019
1018
  const { local, domain, options = {} } = params;
1020
1019
  const mxRecords = (_a = params.mxRecords) !== null && _a !== void 0 ? _a : [];
1021
1020
  const ports = ((_b = options.ports) !== null && _b !== void 0 ? _b : DEFAULT_PORTS).filter((port) => Number.isInteger(port) && port > 0 && port < 65536);
@@ -1047,7 +1046,8 @@ async function verifyMailboxSMTP(params) {
1047
1046
  sequence,
1048
1047
  log,
1049
1048
  catchAllProbeLocal: options.catchAllProbeLocal,
1050
- pipelining: (_h = options.pipelining) !== null && _h !== void 0 ? _h : "auto"
1049
+ pipelining: (_h = options.pipelining) !== null && _h !== void 0 ? _h : "auto",
1050
+ startTls: (_j = options.startTls) !== null && _j !== void 0 ? _j : "auto"
1051
1051
  };
1052
1052
  const verdictCache = cache ? getCacheStore(cache, "smtp") : null;
1053
1053
  const verdictKey = `${primaryMx}:${local}@${domain}`;
@@ -1181,9 +1181,12 @@ class SMTPProbeConnection {
1181
1181
  this.buffer = "";
1182
1182
  this.resolved = false;
1183
1183
  this.currentStepIndex = 0;
1184
+ this.tlsUpgrading = false;
1185
+ this.postUpgradeReEhlo = false;
1184
1186
  this.transcript = [];
1185
1187
  this.commands = [];
1186
1188
  this.supportsPipelining = false;
1189
+ this.supportsStartTls = false;
1187
1190
  this.dualPhase = "idle";
1188
1191
  this.realOutcome = "pending";
1189
1192
  this.probeOutcome = "pending";
@@ -1201,7 +1204,7 @@ class SMTPProbeConnection {
1201
1204
  this.processLine(line);
1202
1205
  }
1203
1206
  };
1204
- const defaultSteps = [exports.SMTPStep.greeting, exports.SMTPStep.ehlo, exports.SMTPStep.mailFrom, exports.SMTPStep.rcptTo];
1207
+ const defaultSteps = [exports.SMTPStep.greeting, exports.SMTPStep.ehlo, exports.SMTPStep.startTls, exports.SMTPStep.mailFrom, exports.SMTPStep.rcptTo];
1205
1208
  this.steps = [...(_b = (_a = p.sequence) === null || _a === void 0 ? void 0 : _a.steps) !== null && _b !== void 0 ? _b : defaultSteps];
1206
1209
  this.isTLS = PORT_TLS[p.port] === true;
1207
1210
  const servername = isIPAddress(p.mxHost) ? void 0 : p.mxHost;
@@ -1221,7 +1224,8 @@ class SMTPProbeConnection {
1221
1224
  this.connect();
1222
1225
  this.armConnectionTimer();
1223
1226
  } catch (error) {
1224
- this.finish(null, `connect_throw:${error instanceof Error ? error.message : "unknown"}`);
1227
+ this.p.log(`connect threw: ${error instanceof Error ? error.message : "unknown"}`);
1228
+ this.finish(null, "connection_error");
1225
1229
  }
1226
1230
  });
1227
1231
  }
@@ -1280,6 +1284,9 @@ class SMTPProbeConnection {
1280
1284
  case exports.SMTPStep.helo:
1281
1285
  this.send(`HELO ${this.p.hostname}`);
1282
1286
  return;
1287
+ case exports.SMTPStep.startTls:
1288
+ this.executeStartTls();
1289
+ return;
1283
1290
  case exports.SMTPStep.mailFrom: {
1284
1291
  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}>`;
1285
1292
  this.send(`MAIL FROM:${from}`);
@@ -1290,6 +1297,73 @@ class SMTPProbeConnection {
1290
1297
  return;
1291
1298
  }
1292
1299
  }
1300
+ /**
1301
+ * Conditional STARTTLS upgrade. Skipped (advances to next step) when:
1302
+ * - already TLS (implicit-TLS port like 465 or already-upgraded)
1303
+ * - `startTls === 'never'`
1304
+ * - `startTls === 'auto'` AND the MX didn't advertise STARTTLS in EHLO
1305
+ *
1306
+ * Sends `STARTTLS` and waits for 220 when:
1307
+ * - `startTls === 'force'` (regardless of advertisement)
1308
+ * - `startTls === 'auto'` AND the MX advertised it
1309
+ *
1310
+ * On 220, `tls.connect()` wraps the existing socket. After the handshake
1311
+ * we re-EHLO (mandatory per RFC 3207 §4.2 — pre-TLS state must be
1312
+ * discarded) before continuing to MAIL FROM.
1313
+ */
1314
+ executeStartTls() {
1315
+ const mode = this.p.startTls;
1316
+ const wantsUpgrade = !this.isTLS && mode !== "never" && (mode === "force" || mode === "auto" && this.supportsStartTls);
1317
+ if (!wantsUpgrade) {
1318
+ this.nextStep();
1319
+ return;
1320
+ }
1321
+ this.send("STARTTLS");
1322
+ }
1323
+ /**
1324
+ * Wrap the plaintext socket with TLS in place. Called after the server
1325
+ * answers our STARTTLS with 220. Detaches the plaintext-socket listeners
1326
+ * (TLS owns the underlying transport now), re-installs them on the wrapped
1327
+ * socket, resets EHLO-derived capabilities, and re-issues EHLO once the
1328
+ * handshake completes (RFC 3207 §4.2 mandates re-EHLO after upgrade —
1329
+ * pre-TLS state must be discarded).
1330
+ */
1331
+ upgradeToTls() {
1332
+ const plainSocket = this.socket;
1333
+ if (!plainSocket) {
1334
+ this.finish(null, "tls_upgrade_failed");
1335
+ return;
1336
+ }
1337
+ const detach = plainSocket.removeAllListeners;
1338
+ if (typeof detach === "function") {
1339
+ detach.call(plainSocket, "data");
1340
+ detach.call(plainSocket, "error");
1341
+ detach.call(plainSocket, "close");
1342
+ detach.call(plainSocket, "timeout");
1343
+ }
1344
+ try {
1345
+ plainSocket.setTimeout(0);
1346
+ } catch {
1347
+ }
1348
+ this.tlsUpgrading = true;
1349
+ const servername = isIPAddress(this.p.mxHost) ? void 0 : this.p.mxHost;
1350
+ const tlsSocket = tls__namespace.connect({ ...this.tlsOptions, socket: plainSocket, servername }, () => {
1351
+ this.tlsUpgrading = false;
1352
+ this.isTLS = true;
1353
+ this.buffer = "";
1354
+ this.supportsStartTls = false;
1355
+ this.supportsPipelining = false;
1356
+ this.postUpgradeReEhlo = true;
1357
+ this.send(`EHLO ${this.p.hostname}`);
1358
+ });
1359
+ this.socket = tlsSocket;
1360
+ this.socket.on("data", this.onData);
1361
+ this.socket.on("error", () => {
1362
+ this.finish(null, this.tlsUpgrading ? "tls_handshake_failed" : "connection_error");
1363
+ });
1364
+ this.socket.on("close", () => this.finish(null, "connection_closed"));
1365
+ this.socket.setTimeout(this.p.timeout, () => this.finish(null, "socket_timeout"));
1366
+ }
1293
1367
  /**
1294
1368
  * Send the dual-probe envelope (real RCPT + probe RCPT + RSET). Pipelined
1295
1369
  * when the MX advertised PIPELINING (or `pipelining: 'force'`); sequential
@@ -1348,10 +1422,13 @@ ${rsetCmd}\r
1348
1422
  }
1349
1423
  if (MULTILINE_RE.test(line)) {
1350
1424
  const step = this.steps[this.currentStepIndex];
1351
- if ((step === exports.SMTPStep.ehlo || step === exports.SMTPStep.helo) && line.startsWith("250-")) {
1425
+ const isEhloLike = step === exports.SMTPStep.ehlo || step === exports.SMTPStep.helo || step === exports.SMTPStep.startTls && this.postUpgradeReEhlo;
1426
+ if (isEhloLike && line.startsWith("250-")) {
1352
1427
  const upper = line.toUpperCase();
1353
1428
  if (upper.includes("PIPELINING"))
1354
1429
  this.supportsPipelining = true;
1430
+ if (upper.includes("STARTTLS"))
1431
+ this.supportsStartTls = true;
1355
1432
  }
1356
1433
  return;
1357
1434
  }
@@ -1386,6 +1463,21 @@ ${rsetCmd}\r
1386
1463
  else
1387
1464
  this.finish(null, "helo_failed");
1388
1465
  return;
1466
+ case exports.SMTPStep.startTls:
1467
+ if (this.postUpgradeReEhlo) {
1468
+ this.postUpgradeReEhlo = false;
1469
+ if (code === 250)
1470
+ this.nextStep();
1471
+ else
1472
+ this.finish(null, "ehlo_failed");
1473
+ return;
1474
+ }
1475
+ if (code === 220) {
1476
+ this.upgradeToTls();
1477
+ } else {
1478
+ this.finish(null, "tls_upgrade_failed");
1479
+ }
1480
+ return;
1389
1481
  case exports.SMTPStep.mailFrom:
1390
1482
  if (code === 250)
1391
1483
  this.nextStep();
@@ -2547,6 +2639,36 @@ function isSpamName(name) {
2547
2639
  return true;
2548
2640
  }
2549
2641
 
2642
+ const REFINEMENT_TABLE = {
2643
+ // X.1.x — addressing
2644
+ "5.1.1": "mailbox_does_not_exist",
2645
+ "5.1.2": "bad_destination_system",
2646
+ "5.1.3": "bad_destination_address",
2647
+ "5.1.6": "mailbox_moved",
2648
+ "5.1.10": "recipient_address_has_null_mx",
2649
+ // X.2.x — mailbox status
2650
+ "5.2.0": "mailbox_status_other",
2651
+ "5.2.1": "mailbox_disabled",
2652
+ "5.2.2": "mailbox_full",
2653
+ "5.2.3": "message_too_long",
2654
+ "5.2.4": "mailing_list_expansion_problem",
2655
+ // X.4.x — network / routing
2656
+ "4.4.1": "no_answer_from_host",
2657
+ "4.4.2": "bad_connection",
2658
+ // X.7.x — security / policy
2659
+ "5.7.0": "security_other",
2660
+ "5.7.1": "delivery_not_authorized",
2661
+ "5.7.25": "no_reverse_dns",
2662
+ "5.7.26": "multiple_authentication_failures"
2663
+ };
2664
+ function refineReasonByEnhancedStatus(reason, enhancedStatus) {
2665
+ var _a;
2666
+ const base = reason !== null && reason !== void 0 ? reason : "unknown";
2667
+ if (!enhancedStatus)
2668
+ return base;
2669
+ return (_a = REFINEMENT_TABLE[enhancedStatus]) !== null && _a !== void 0 ? _a : base;
2670
+ }
2671
+
2550
2672
  const NETWORK_ERROR_PATTERNS = [
2551
2673
  "etimedout",
2552
2674
  "econnrefused",
@@ -2677,6 +2799,7 @@ exports.isSpamName = isSpamName;
2677
2799
  exports.isValidEmail = isValidEmail;
2678
2800
  exports.isValidEmailDomain = isValidEmailDomain;
2679
2801
  exports.parseSmtpError = parseSmtpError;
2802
+ exports.refineReasonByEnhancedStatus = refineReasonByEnhancedStatus;
2680
2803
  exports.suggestDomain = suggestDomain;
2681
2804
  exports.suggestEmailDomain = suggestEmailDomain;
2682
2805
  exports.verifyEmail = verifyEmail;