UNPKG

mailproof

Version:

A lightweight and efficient email validation library that checks email format, MX records, and SMTP response.

181 lines (180 loc) 7.84 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EmailValidator = void 0; const promises_1 = require("dns/promises"); const net_1 = require("net"); const node_fetch_1 = __importDefault(require("node-fetch")); const DEFAULT_OPTIONS = { checkMX: true, checkSMTP: true, timeout: 10000, dnsServers: ["1.1.1.1", "8.8.8.8"], }; class EmailValidator { constructor(options) { this.disposableDomains = new Set(); if (!options.smtpFrom) { throw new Error("smtpFrom is required in options"); } const modifiedOptions = Object.assign(Object.assign({}, DEFAULT_OPTIONS), options); if (modifiedOptions.checkSMTP && !modifiedOptions.checkMX) { throw new Error("checkSMTP requires checkMX to be true. Please enable checkMX or disable checkSMTP."); } this.options = modifiedOptions; this.loadDisposableDomains(); } loadDisposableDomains() { return __awaiter(this, void 0, void 0, function* () { try { const response = yield (0, node_fetch_1.default)("https://disposable.github.io/disposable-email-domains/domains.txt"); const text = yield response.text(); this.disposableDomains = new Set(text.split("\n").map((d) => d.trim())); } catch (error) { console.error("Failed to fetch disposable email domains:", error); } }); } validateFormat(email) { const regex = /^(([^<>()[\]\\.,;:\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,}))$/; return regex.test(email); } checkMXRecords(domain) { return __awaiter(this, void 0, void 0, function* () { const resolver = new promises_1.Resolver(); resolver.setServers(this.options.dnsServers); return resolver.resolveMx(domain); }); } testSMTP(email, mxRecords) { return __awaiter(this, void 0, void 0, function* () { const [, domain] = email.split("@"); for (const record of mxRecords.sort((a, b) => a.priority - b.priority)) { const socket = new net_1.Socket(); socket.setTimeout(this.options.timeout); try { yield new Promise((resolve, reject) => { const onError = (err) => { socket.removeListener("connect", onConnect); reject(err); }; const onConnect = () => { socket.removeListener("error", onError); resolve(); }; const onTimeout = () => { socket.removeListener("connect", onConnect); socket.removeListener("error", onError); reject(new Error("Connection timeout")); }; socket.once("error", onError); socket.once("connect", onConnect); socket.once("timeout", onTimeout); socket.connect(587, record.exchange); }); yield this.expectResponse(socket, 220); yield this.sendCommand(socket, `EHLO ${domain}\r\n`, 250); yield this.sendCommand(socket, `MAIL FROM:<${this.options.smtpFrom}>\r\n`, 250); const response = yield this.sendCommand(socket, `RCPT TO:<${email}>\r\n`, 250); return response.startsWith("250"); } catch (error) { continue; } finally { socket.end(); } } return false; }); } sendCommand(socket, command, expectedCode) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { socket.write(command, "utf8", () => { socket.once("data", (data) => { const response = data.toString(); if (response.startsWith(expectedCode.toString())) { resolve(response); } else { reject(new Error(`SMTP Error: ${response}`)); } }); }); }); }); } expectResponse(socket, expectedCode) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { socket.once("data", (data) => { const response = data.toString(); if (response.startsWith(expectedCode.toString())) { resolve(); } else { reject(new Error(`Unexpected SMTP response: ${response}`)); } }); }); }); } validate(email) { return __awaiter(this, void 0, void 0, function* () { var _a; const errors = []; if (!this.validateFormat(email)) { errors.push("Invalid email format"); return { valid: false, errors }; } const domain = (_a = email.split("@")[1]) === null || _a === void 0 ? void 0 : _a.toLowerCase(); if (!domain) { errors.push("Invalid email domain"); return { valid: false, errors }; } if (this.disposableDomains.has(domain.toLowerCase())) errors.push("Disposable email domain"); let mxRecords = []; let mxValid = true; if (this.options.checkMX) { try { mxRecords = yield this.checkMXRecords(domain); if (mxRecords.length === 0) { errors.push("No MX records found"); mxValid = false; } } catch (_b) { errors.push("MX record validation failed"); mxValid = false; } } if (this.options.checkSMTP && mxValid && mxRecords.length > 0) { try { const smtpValid = yield this.testSMTP(email, mxRecords); if (!smtpValid) errors.push("SMTP validation failed"); } catch (_c) { errors.push("SMTP verification error"); } } return { valid: errors.length === 0, errors }; }); } } exports.EmailValidator = EmailValidator; exports.default = EmailValidator;