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
JavaScript
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;
;