@emailcheck/email-validator-js 4.0.0-beta.1 → 4.0.0-beta.2
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/README.md +35 -0
- package/dist/cli/index.js +103 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +130 -6
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +130 -5
- package/dist/index.js.map +1 -1
- package/dist/refine-reason.d.ts +1 -0
- package/dist/types.d.ts +25 -6
- package/package.json +1 -1
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
|
@@ -175,6 +175,7 @@ var SMTPStep;
|
|
|
175
175
|
SMTPStep2["greeting"] = "GREETING";
|
|
176
176
|
SMTPStep2["ehlo"] = "EHLO";
|
|
177
177
|
SMTPStep2["helo"] = "HELO";
|
|
178
|
+
SMTPStep2["startTls"] = "STARTTLS";
|
|
178
179
|
SMTPStep2["mailFrom"] = "MAIL_FROM";
|
|
179
180
|
SMTPStep2["rcptTo"] = "RCPT_TO";
|
|
180
181
|
})(SMTPStep || (SMTPStep = {}));
|
|
@@ -993,7 +994,7 @@ function defaultProbeLocal() {
|
|
|
993
994
|
return `${randomBytes(8).toString("hex")}-noexist`;
|
|
994
995
|
}
|
|
995
996
|
async function verifyMailboxSMTP(params) {
|
|
996
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
997
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
997
998
|
const { local, domain, options = {} } = params;
|
|
998
999
|
const mxRecords = (_a = params.mxRecords) !== null && _a !== void 0 ? _a : [];
|
|
999
1000
|
const ports = ((_b = options.ports) !== null && _b !== void 0 ? _b : DEFAULT_PORTS).filter((port) => Number.isInteger(port) && port > 0 && port < 65536);
|
|
@@ -1025,7 +1026,8 @@ async function verifyMailboxSMTP(params) {
|
|
|
1025
1026
|
sequence,
|
|
1026
1027
|
log,
|
|
1027
1028
|
catchAllProbeLocal: options.catchAllProbeLocal,
|
|
1028
|
-
pipelining: (_h = options.pipelining) !== null && _h !== void 0 ? _h : "auto"
|
|
1029
|
+
pipelining: (_h = options.pipelining) !== null && _h !== void 0 ? _h : "auto",
|
|
1030
|
+
startTls: (_j = options.startTls) !== null && _j !== void 0 ? _j : "auto"
|
|
1029
1031
|
};
|
|
1030
1032
|
const verdictCache = cache ? getCacheStore(cache, "smtp") : null;
|
|
1031
1033
|
const verdictKey = `${primaryMx}:${local}@${domain}`;
|
|
@@ -1159,9 +1161,12 @@ class SMTPProbeConnection {
|
|
|
1159
1161
|
this.buffer = "";
|
|
1160
1162
|
this.resolved = false;
|
|
1161
1163
|
this.currentStepIndex = 0;
|
|
1164
|
+
this.tlsUpgrading = false;
|
|
1165
|
+
this.postUpgradeReEhlo = false;
|
|
1162
1166
|
this.transcript = [];
|
|
1163
1167
|
this.commands = [];
|
|
1164
1168
|
this.supportsPipelining = false;
|
|
1169
|
+
this.supportsStartTls = false;
|
|
1165
1170
|
this.dualPhase = "idle";
|
|
1166
1171
|
this.realOutcome = "pending";
|
|
1167
1172
|
this.probeOutcome = "pending";
|
|
@@ -1179,7 +1184,7 @@ class SMTPProbeConnection {
|
|
|
1179
1184
|
this.processLine(line);
|
|
1180
1185
|
}
|
|
1181
1186
|
};
|
|
1182
|
-
const defaultSteps = [SMTPStep.greeting, SMTPStep.ehlo, SMTPStep.mailFrom, SMTPStep.rcptTo];
|
|
1187
|
+
const defaultSteps = [SMTPStep.greeting, SMTPStep.ehlo, SMTPStep.startTls, SMTPStep.mailFrom, SMTPStep.rcptTo];
|
|
1183
1188
|
this.steps = [...(_b = (_a = p.sequence) === null || _a === void 0 ? void 0 : _a.steps) !== null && _b !== void 0 ? _b : defaultSteps];
|
|
1184
1189
|
this.isTLS = PORT_TLS[p.port] === true;
|
|
1185
1190
|
const servername = isIPAddress(p.mxHost) ? void 0 : p.mxHost;
|
|
@@ -1199,7 +1204,8 @@ class SMTPProbeConnection {
|
|
|
1199
1204
|
this.connect();
|
|
1200
1205
|
this.armConnectionTimer();
|
|
1201
1206
|
} catch (error) {
|
|
1202
|
-
this.
|
|
1207
|
+
this.p.log(`connect threw: ${error instanceof Error ? error.message : "unknown"}`);
|
|
1208
|
+
this.finish(null, "connection_error");
|
|
1203
1209
|
}
|
|
1204
1210
|
});
|
|
1205
1211
|
}
|
|
@@ -1258,6 +1264,9 @@ class SMTPProbeConnection {
|
|
|
1258
1264
|
case SMTPStep.helo:
|
|
1259
1265
|
this.send(`HELO ${this.p.hostname}`);
|
|
1260
1266
|
return;
|
|
1267
|
+
case SMTPStep.startTls:
|
|
1268
|
+
this.executeStartTls();
|
|
1269
|
+
return;
|
|
1261
1270
|
case SMTPStep.mailFrom: {
|
|
1262
1271
|
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
1272
|
this.send(`MAIL FROM:${from}`);
|
|
@@ -1268,6 +1277,73 @@ class SMTPProbeConnection {
|
|
|
1268
1277
|
return;
|
|
1269
1278
|
}
|
|
1270
1279
|
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Conditional STARTTLS upgrade. Skipped (advances to next step) when:
|
|
1282
|
+
* - already TLS (implicit-TLS port like 465 or already-upgraded)
|
|
1283
|
+
* - `startTls === 'never'`
|
|
1284
|
+
* - `startTls === 'auto'` AND the MX didn't advertise STARTTLS in EHLO
|
|
1285
|
+
*
|
|
1286
|
+
* Sends `STARTTLS` and waits for 220 when:
|
|
1287
|
+
* - `startTls === 'force'` (regardless of advertisement)
|
|
1288
|
+
* - `startTls === 'auto'` AND the MX advertised it
|
|
1289
|
+
*
|
|
1290
|
+
* On 220, `tls.connect()` wraps the existing socket. After the handshake
|
|
1291
|
+
* we re-EHLO (mandatory per RFC 3207 §4.2 — pre-TLS state must be
|
|
1292
|
+
* discarded) before continuing to MAIL FROM.
|
|
1293
|
+
*/
|
|
1294
|
+
executeStartTls() {
|
|
1295
|
+
const mode = this.p.startTls;
|
|
1296
|
+
const wantsUpgrade = !this.isTLS && mode !== "never" && (mode === "force" || mode === "auto" && this.supportsStartTls);
|
|
1297
|
+
if (!wantsUpgrade) {
|
|
1298
|
+
this.nextStep();
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
this.send("STARTTLS");
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Wrap the plaintext socket with TLS in place. Called after the server
|
|
1305
|
+
* answers our STARTTLS with 220. Detaches the plaintext-socket listeners
|
|
1306
|
+
* (TLS owns the underlying transport now), re-installs them on the wrapped
|
|
1307
|
+
* socket, resets EHLO-derived capabilities, and re-issues EHLO once the
|
|
1308
|
+
* handshake completes (RFC 3207 §4.2 mandates re-EHLO after upgrade —
|
|
1309
|
+
* pre-TLS state must be discarded).
|
|
1310
|
+
*/
|
|
1311
|
+
upgradeToTls() {
|
|
1312
|
+
const plainSocket = this.socket;
|
|
1313
|
+
if (!plainSocket) {
|
|
1314
|
+
this.finish(null, "tls_upgrade_failed");
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
const detach = plainSocket.removeAllListeners;
|
|
1318
|
+
if (typeof detach === "function") {
|
|
1319
|
+
detach.call(plainSocket, "data");
|
|
1320
|
+
detach.call(plainSocket, "error");
|
|
1321
|
+
detach.call(plainSocket, "close");
|
|
1322
|
+
detach.call(plainSocket, "timeout");
|
|
1323
|
+
}
|
|
1324
|
+
try {
|
|
1325
|
+
plainSocket.setTimeout(0);
|
|
1326
|
+
} catch {
|
|
1327
|
+
}
|
|
1328
|
+
this.tlsUpgrading = true;
|
|
1329
|
+
const servername = isIPAddress(this.p.mxHost) ? void 0 : this.p.mxHost;
|
|
1330
|
+
const tlsSocket = tls.connect({ ...this.tlsOptions, socket: plainSocket, servername }, () => {
|
|
1331
|
+
this.tlsUpgrading = false;
|
|
1332
|
+
this.isTLS = true;
|
|
1333
|
+
this.buffer = "";
|
|
1334
|
+
this.supportsStartTls = false;
|
|
1335
|
+
this.supportsPipelining = false;
|
|
1336
|
+
this.postUpgradeReEhlo = true;
|
|
1337
|
+
this.send(`EHLO ${this.p.hostname}`);
|
|
1338
|
+
});
|
|
1339
|
+
this.socket = tlsSocket;
|
|
1340
|
+
this.socket.on("data", this.onData);
|
|
1341
|
+
this.socket.on("error", () => {
|
|
1342
|
+
this.finish(null, this.tlsUpgrading ? "tls_handshake_failed" : "connection_error");
|
|
1343
|
+
});
|
|
1344
|
+
this.socket.on("close", () => this.finish(null, "connection_closed"));
|
|
1345
|
+
this.socket.setTimeout(this.p.timeout, () => this.finish(null, "socket_timeout"));
|
|
1346
|
+
}
|
|
1271
1347
|
/**
|
|
1272
1348
|
* Send the dual-probe envelope (real RCPT + probe RCPT + RSET). Pipelined
|
|
1273
1349
|
* when the MX advertised PIPELINING (or `pipelining: 'force'`); sequential
|
|
@@ -1326,10 +1402,13 @@ ${rsetCmd}\r
|
|
|
1326
1402
|
}
|
|
1327
1403
|
if (MULTILINE_RE.test(line)) {
|
|
1328
1404
|
const step = this.steps[this.currentStepIndex];
|
|
1329
|
-
|
|
1405
|
+
const isEhloLike = step === SMTPStep.ehlo || step === SMTPStep.helo || step === SMTPStep.startTls && this.postUpgradeReEhlo;
|
|
1406
|
+
if (isEhloLike && line.startsWith("250-")) {
|
|
1330
1407
|
const upper = line.toUpperCase();
|
|
1331
1408
|
if (upper.includes("PIPELINING"))
|
|
1332
1409
|
this.supportsPipelining = true;
|
|
1410
|
+
if (upper.includes("STARTTLS"))
|
|
1411
|
+
this.supportsStartTls = true;
|
|
1333
1412
|
}
|
|
1334
1413
|
return;
|
|
1335
1414
|
}
|
|
@@ -1364,6 +1443,21 @@ ${rsetCmd}\r
|
|
|
1364
1443
|
else
|
|
1365
1444
|
this.finish(null, "helo_failed");
|
|
1366
1445
|
return;
|
|
1446
|
+
case SMTPStep.startTls:
|
|
1447
|
+
if (this.postUpgradeReEhlo) {
|
|
1448
|
+
this.postUpgradeReEhlo = false;
|
|
1449
|
+
if (code === 250)
|
|
1450
|
+
this.nextStep();
|
|
1451
|
+
else
|
|
1452
|
+
this.finish(null, "ehlo_failed");
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
if (code === 220) {
|
|
1456
|
+
this.upgradeToTls();
|
|
1457
|
+
} else {
|
|
1458
|
+
this.finish(null, "tls_upgrade_failed");
|
|
1459
|
+
}
|
|
1460
|
+
return;
|
|
1367
1461
|
case SMTPStep.mailFrom:
|
|
1368
1462
|
if (code === 250)
|
|
1369
1463
|
this.nextStep();
|
|
@@ -2525,6 +2619,36 @@ function isSpamName(name) {
|
|
|
2525
2619
|
return true;
|
|
2526
2620
|
}
|
|
2527
2621
|
|
|
2622
|
+
const REFINEMENT_TABLE = {
|
|
2623
|
+
// X.1.x — addressing
|
|
2624
|
+
"5.1.1": "mailbox_does_not_exist",
|
|
2625
|
+
"5.1.2": "bad_destination_system",
|
|
2626
|
+
"5.1.3": "bad_destination_address",
|
|
2627
|
+
"5.1.6": "mailbox_moved",
|
|
2628
|
+
"5.1.10": "recipient_address_has_null_mx",
|
|
2629
|
+
// X.2.x — mailbox status
|
|
2630
|
+
"5.2.0": "mailbox_status_other",
|
|
2631
|
+
"5.2.1": "mailbox_disabled",
|
|
2632
|
+
"5.2.2": "mailbox_full",
|
|
2633
|
+
"5.2.3": "message_too_long",
|
|
2634
|
+
"5.2.4": "mailing_list_expansion_problem",
|
|
2635
|
+
// X.4.x — network / routing
|
|
2636
|
+
"4.4.1": "no_answer_from_host",
|
|
2637
|
+
"4.4.2": "bad_connection",
|
|
2638
|
+
// X.7.x — security / policy
|
|
2639
|
+
"5.7.0": "security_other",
|
|
2640
|
+
"5.7.1": "delivery_not_authorized",
|
|
2641
|
+
"5.7.25": "no_reverse_dns",
|
|
2642
|
+
"5.7.26": "multiple_authentication_failures"
|
|
2643
|
+
};
|
|
2644
|
+
function refineReasonByEnhancedStatus(reason, enhancedStatus) {
|
|
2645
|
+
var _a;
|
|
2646
|
+
const base = reason !== null && reason !== void 0 ? reason : "unknown";
|
|
2647
|
+
if (!enhancedStatus)
|
|
2648
|
+
return base;
|
|
2649
|
+
return (_a = REFINEMENT_TABLE[enhancedStatus]) !== null && _a !== void 0 ? _a : base;
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2528
2652
|
const NETWORK_ERROR_PATTERNS = [
|
|
2529
2653
|
"etimedout",
|
|
2530
2654
|
"econnrefused",
|
|
@@ -2628,5 +2752,5 @@ function parseSmtpError(errorMessage) {
|
|
|
2628
2752
|
return { isDisabled, hasFullInbox, isCatchAll, isInvalid };
|
|
2629
2753
|
}
|
|
2630
2754
|
|
|
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 };
|
|
2755
|
+
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
2756
|
//# sourceMappingURL=index.esm.js.map
|