@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/README.md +35 -0
- package/dist/cli/index.js +103 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +130 -8
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +130 -7
- package/dist/index.js.map +1 -1
- package/dist/refine-reason.d.ts +1 -0
- package/dist/serverless/adapters/aws-lambda.cjs.js.map +1 -1
- package/dist/serverless/adapters/aws-lambda.esm.js.map +1 -1
- package/dist/serverless/index.cjs.js.map +1 -1
- package/dist/serverless/index.esm.js.map +1 -1
- package/dist/types.d.ts +28 -19
- 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
|
@@ -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.
|
|
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
|
-
|
|
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
|