UNPKG

email-domain-verifier

Version:
157 lines (135 loc) 4.85 kB
import { resolveMx } from 'dns' import { createConnection } from 'net' import { EmailVerificationOptions, EnsuredEmailVerificationOptions } from './types/EmailVerificationOptions' import { EmailVerificationResult, EmailVerificationDetails } from './types/EmailVerificationResult' const LRU = require('lru-cache') const cache = new LRU({ max: 1000, ttl: 24 * 60 * 60 * 1000 }) const verifyEmailDomain = async (emailOrDomain: string, options: EmailVerificationOptions = {}): Promise<EmailVerificationResult> => { const ensuredOptions = verifier.ensureOptions(options) const { mxVerificationSucceed, smtpVerificationSucceed } = await verifier.verify(verifier.parseDomainFromEmail(emailOrDomain), ensuredOptions) if (ensuredOptions.requireSmtpOrMx) { return { verified: mxVerificationSucceed || (smtpVerificationSucceed ?? false), mxVerificationSucceed, smtpVerificationSucceed } } return { verified: (mxVerificationSucceed || ensuredOptions.mxNotRequired) && (smtpVerificationSucceed || ensuredOptions.smtpNotRequired), mxVerificationSucceed, smtpVerificationSucceed } } const ensureOptions = (options: EmailVerificationOptions): EnsuredEmailVerificationOptions => { return { smtpNotRequired: ('smtpNotRequired' in options && options.smtpNotRequired) ?? false, mxNotRequired: ('mxNotRequired' in options && options.mxNotRequired) ?? false, requireSmtpOrMx: ('requireSmtpOrMx' in options && options.requireSmtpOrMx) ?? false, smtpConnectionTimeout: options.smtpConnectionTimeout ?? 1000, useCache: options.useCache ?? true } } const parseDomainFromEmail = (emailOrDomain: string): string => { const sepCount = (emailOrDomain.match(/@/g) || []).length if (sepCount === 0) { return emailOrDomain } return emailOrDomain.split('@')[sepCount] } const verify = async (domain: string, options: EnsuredEmailVerificationOptions): Promise<EmailVerificationDetails> => { if (options.useCache) { const result = cache.get(getCacheKey(domain, options)) if (result) { return result as EmailVerificationDetails } } const exchangeServers = await verifier.getExchangeServers(domain) const mxVerificationSucceed = Boolean(exchangeServers.length) if ((options.requireSmtpOrMx && mxVerificationSucceed) || options.smtpNotRequired) { const result = { mxVerificationSucceed } if (mxVerificationSucceed) { cache.set(getCacheKey(domain, options), result) } return result } const smtpHosts = verifier.generateSmtpHostList(domain, exchangeServers) const smtpVerificationSucceed = await verifier.isSmtpServerRunning(smtpHosts, options.smtpConnectionTimeout ?? 1000) const result = { mxVerificationSucceed, smtpVerificationSucceed } if (result.mxVerificationSucceed && result.smtpVerificationSucceed) { cache.set(getCacheKey(domain, options), result) } return result } const getCacheKey = (domain: string, options: EnsuredEmailVerificationOptions): string => { return `${domain}-${options.requireSmtpOrMx}-${options.mxNotRequired}-${options.smtpNotRequired}` } const getExchangeServers = (domain: string): Promise<string[]> => { return new Promise<string[]>((resolve, reject) => { resolveMx(domain, (err, addresses) => { if (err) { const expectedErrors = ['ESERVFAIL', 'ENODATA', 'ENOTFOUND'] if (err.code && expectedErrors.includes(err.code)) { return resolve([]) } return reject(err) } return resolve( addresses .sort((a, b) => a.priority - b.priority) .map(a => a.exchange) ) }) }) } const generateSmtpHostList = (domain: string, exchangeServers: string[]): string[] => { return [...exchangeServers, `smtp.${domain}`, domain] } const isSmtpServerRunning = async (hosts: string[], timeout: number): Promise<boolean> => { const portsToCheck = [25, 465, 587, 2525] for (const host of hosts) { for (const port of portsToCheck) { const res = await verifier.isPortOpen(host, port, timeout) if (res) { return true } } } return false } const isPortOpen = (host: string, port: number, timeout: number): Promise<boolean> => { return new Promise<boolean>(function (resolve, reject) { const timer = setTimeout(() => { resolve(false) socket.destroy() }, timeout) const socket = createConnection(port, host, () => { clearTimeout(timer) resolve(true) socket.destroy() }) socket.on('error', function (err) { clearTimeout(timer) resolve(false) socket.destroy() }) }) } const verifier = { verifyEmailDomain, ensureOptions, parseDomainFromEmail, verify, getExchangeServers, generateSmtpHostList, isSmtpServerRunning, isPortOpen } export default verifier