ã¡ãŒã«æ€èšŒã¯ããã¹ãŠã®éçºè ãçè§£ããæ£ããå®è£ ããå¿ èŠãããçŸä»£ã®ãŠã§ãã¢ããªã±ãŒã·ã§ã³ã®éèŠãªã³ã³ããŒãã³ãã§ãããŠãŒã¶ãŒç»é²ã·ã¹ãã ããã¥ãŒã¹ã¬ã¿ãŒãã©ãããã©ãŒã ãeã³ããŒã¹ã¢ããªã±ãŒã·ã§ã³ã®ããããæ§ç¯ããå Žåã§ããå ç¢ãªã¡ãŒã«æ€èšŒã®å®è£ ã«ãããã¢ããªã±ãŒã·ã§ã³ãç¡å¹ãªããŒã¿ããä¿è·ããããŠã³ã¹çãåæžããå šäœçãªå°éæ§ãåäžãããŸãããã®å æ¬çãªã¬ã€ãã¯ãéçºè ããããã§ãã·ã§ãã«ã°ã¬ãŒãã®ã¡ãŒã«æ€èšŒããŒãããå®è£ ããããã«å¿ èŠãªãã¹ãŠãæäŸããŸãã
éçºè ãã¡ãŒã«æ€èšŒãå¿ èŠãšããçç±
ã¡ãŒã«æ€èšŒã®éèŠæ§ãçè§£ããããšã§ãéçºè ã¯å®è£ æŠç¥ãšãªãœãŒã¹é åã«ã€ããŠæ å ±ã«åºã¥ããæææ±ºå®ãè¡ãããšãã§ããŸãã
ã¡ãŒã«æ€èšŒã®ããžãã¹ã±ãŒã¹
ç¡å¹ãªã¡ãŒã«ã¢ãã¬ã¹ã¯ãç¡é§ãªããŒã±ãã£ã³ã°æ¯åºãéä¿¡è è©äŸ¡ã®äœäžã顧客ãšã³ã²ãŒãžã¡ã³ãæ©äŒã®åªå€±ãéããŠãäŒæ¥ã«å¹Žéæ°çŸäžãã«ã®ã³ã¹ãããããŠããŸãããŠãŒã¶ãŒãç»é²æã«èª€ã£ãã¡ãŒã«ã¢ãã¬ã¹ãå ¥åããå Žåããããã¿ã€ããã¹ã«ãããã®ã§ãããæå³çãªåœã®ã¢ãã¬ã¹ã§ããããã®åœ±é¿ã¯ã·ã¹ãã å šäœã«æ³¢åããŸãã
GmailãOutlookãYahoo ãªã©ã®ã¡ãŒã«ãµãŒãã¹ãããã€ããŒã¯ãéä¿¡è è©äŸ¡ã¡ããªã¯ã¹ãå³éã«ç£èŠããŠããŸããã¢ããªã±ãŒã·ã§ã³ãç¡å¹ãªã¢ãã¬ã¹ã«ã¡ãŒã«ãéä¿¡ãããšããããã¯ããŠã³ã¹ããã¯ãããéä¿¡è ã¹ã³ã¢ã«æªåœ±é¿ãäžããŸããéä¿¡è è©äŸ¡ãäœããšãæ£åœãªã¡ãŒã«ãã¹ãã ãã©ã«ããŒã«å ¥ãå¯èœæ§ãé«ããªãããã¹ãŠã®ã¡ãŒã«ã³ãã¥ãã±ãŒã·ã§ã³ã®å¹æãäœäžããŸãã
éçºè ã«ãšã£ãŠããšã³ããªãŒãã€ã³ãã§ã®ã¡ãŒã«æ€èšŒã®å®è£ ã¯ããããã®åé¡ãçºçããåã«é²æ¢ããŸãããŠãŒã¶ãŒãµã€ã³ã¢ããæã«ãªã¢ã«ã¿ã€ã ã§ã¡ãŒã«ã¢ãã¬ã¹ãæ€èšŒããããšã§ãããŒã¿ããŒã¹ã«æåããæ£åœã§é ä¿¡å¯èœãªã¢ãã¬ã¹ã®ã¿ãå«ãŸããããšãä¿èšŒããŸãã
ã¡ãŒã«æ€èšŒã®æè¡çå©ç¹
ããžãã¹ã¡ããªã¯ã¹ä»¥å€ã«ããã¡ãŒã«æ€èšŒã¯ã¢ããªã±ãŒã·ã§ã³ã®å質ãšä¿¡é Œæ§ãåäžãããéèŠãªæè¡çå©ç¹ãæäŸããŸããã¯ãªãŒã³ãªã¡ãŒã«ããŒã¿ã¯ãåœã¢ã«ãŠã³ãã«ããããŒã¿ããŒã¹ã®è¥å€§åãåæžããã¯ãšãªããã©ãŒãã³ã¹ãåäžããããŠãŒã¶ãŒç®¡çãç°¡çŽ åããŸãã
ã¡ãŒã«æ€èšŒã¯ãã¢ã«ãŠã³ãåææ»æã鲿¢ãããããç»é²ã®å¹æãäœæžããããšã§ã»ãã¥ãªãã£ã匷åããŸããã¬ãŒãå¶éãCAPTCHAãªã©ã®ä»ã®ã»ãã¥ãªãã£å¯Ÿçãšçµã¿åããããšãã¡ãŒã«æ€èšŒã¯èªååãããæªçšã«å¯Ÿããå ç¢ãªé²åŸ¡ãäœæããŸãã
ã¡ãŒã«æ€èšŒã¢ãŒããã¯ãã£ã®æŠèŠ
å®è£ ã®è©³çްã«å ¥ãåã«ãéçºè ã¯å®å šãªã¡ãŒã«æ€èšŒã¢ãŒããã¯ãã£ãšãç°ãªãã³ã³ããŒãã³ããã©ã®ããã«é£æºããããçè§£ããå¿ èŠããããŸãã
å€å±€æ€èšŒã¢ãããŒã
ãããã§ãã·ã§ãã«ãªã¡ãŒã«æ€èšŒã·ã¹ãã ã¯ãè€æ°ã®æ€èšŒã¬ã€ã€ãŒãå®è£ ãããããããç°ãªãã¿ã€ãã®ç¡å¹ãªã¢ãã¬ã¹ããã£ããããŸãããã®éå±€åãããã¢ãããŒãã¯ãããã©ãŒãã³ã¹ãæé©åããªãã粟床ãæå€§åããŸãã
æåã®ã¬ã€ã€ãŒã¯æ§ææ€èšŒãå®è¡ããã¡ãŒã«ã¢ãã¬ã¹ã RFC 5321 ããã³ RFC 5322 æšæºã«æºæ ããŠããããšã確èªããŸãããã®é«éã§ããŒã«ã«ãªæ€èšŒã¯ããããã¯ãŒã¯ãªã¯ãšã¹ããªãã§æãããªãã©ãŒããããšã©ãŒããã£ããããŸãã
2çªç®ã®ã¬ã€ã€ãŒã¯ DNS æ€èšŒãå®è¡ããMX ã¬ã³ãŒããã¯ãšãªããŠã¡ãŒã«ãã¡ã€ã³ãã¡ãŒã«ãåä¿¡ã§ããããšã確èªããŸãããã®ãããã¯ãŒã¯ããŒã¹ã®æ€èšŒã¯ãååšããªããã¡ã€ã³ãé©åãªã¡ãŒã«èšå®ããªããã¡ã€ã³ããã£ããããŸãã
3çªç®ã®ã¬ã€ã€ãŒã¯ SMTP æ€èšŒãå®è¡ããåä¿¡è ã®ã¡ãŒã«ãµãŒããŒã«æ¥ç¶ããŠç¹å®ã®ã¡ãŒã«ããã¯ã¹ãååšããããšã確èªããŸããããã¯æé«ã®ç²ŸåºŠãæäŸããŸããããããã¯ãããªãããã«æ éãªå®è£ ãå¿ èŠã§ãã
åææ€èšŒãšéåææ€èšŒ
éçºè ã¯ããã©ãŒã éä¿¡äžã®åææ€èšŒãšéä¿¡åŸã®éåææ€èšŒã®ã©ã¡ãããæ±ºå®ããå¿ èŠããããŸããåã¢ãããŒãã«ã¯æç¢ºãªå©ç¹ãšãã¬ãŒããªãããããŸãã
åææ€èšŒã¯ãŠãŒã¶ãŒã«å³åº§ã®ãã£ãŒãããã¯ãæäŸããç¡å¹ãªã¢ãã¬ã¹ãã·ã¹ãã ã«å ¥ãã®ãé²ããŸãããã ããSMTP æ€èšŒã«ã¯æ°ç§ãããå¯èœæ§ããããç»é²äžã«ãŠãŒã¶ãŒãã€ã©ã€ã©ãããå¯èœæ§ããããŸãã
éåææ€èšŒã¯ã¢ãã¬ã¹ãå³åº§ã«åãå ¥ããããã¯ã°ã©ãŠã³ãã§æ€èšŒããŸããããã«ããããŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ãåäžããŸãããéä¿¡åŸã«æ€èšŒã«å€±æããã¢ãã¬ã¹ãåŠçããããã®è¿œå ã®ããžãã¯ãå¿ èŠã§ãã
å€ãã®æ¬çªã·ã¹ãã ã¯ãé«éãªæ§ææ€èšŒãš DNS æ€èšŒãåæçã«å®è¡ããSMTP æ€èšŒãããã¯ã°ã©ãŠã³ãåŠçã«å»¶æãããã€ããªããã¢ãããŒãã䜿çšããŠããŸãã
æ§ææ€èšŒã®å®è£
æ§ææ€èšŒã¯ã¡ãŒã«æ€èšŒã®åºç€ã§ãããé«äŸ¡ãªãããã¯ãŒã¯æäœãå®è¡ããåã«äžæ£ãªã¢ãã¬ã¹ããã£ããããŸãã
ã¡ãŒã«ã¢ãã¬ã¹æ§é ã®çè§£
æå¹ãªã¡ãŒã«ã¢ãã¬ã¹ã¯ãããŒã«ã«éšåã@ èšå·ããã¡ã€ã³éšåã§æ§æãããŸããå®å šãª RFC 仿§ã§ã¯è€éãªåœ¢åŒãèš±å¯ãããŠããŸãããå®çšçãªæ€èšŒã¯äžè¬çã«åãå ¥ããããŠãããã¿ãŒã³ã«çŠç¹ãåœãŠãã¹ãã§ãã
ããŒã«ã«éšåã«ã¯ãè±æ°åããããããã€ãã³ãã¢ã³ããŒã¹ã³ã¢ããã©ã¹èšå·ãå«ããããšãã§ããŸãããã¡ã€ã³éšåã¯ããã¡ã€ã³ãšãããã¬ãã«ãã¡ã€ã³ãåºåãå°ãªããšã1ã€ã®ããããæã€æå¹ãªãã¡ã€ã³åã§ãªããã°ãªããŸããã
æ£èŠè¡šçŸããŒã¹ã®æ€èšŒ
æ£èŠè¡šçŸã¯ãé«éã§æè»ãªã¡ãŒã«æ€èšŒãæäŸããŸãããã ãããã¹ãŠã®æå¹ãªã¢ãã¬ã¹ãæ£ããæ€èšŒããç¡å¹ãªã¢ãã¬ã¹ãæåŠããæ£èŠè¡šçŸãäœæããããšã¯ãé©ãã»ã©è€éã§ãã
// JavaScriptçšã®å®çšçãªã¡ãŒã«æ€èšŒæ£èŠè¡šçŸ
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
function validateEmailSyntax(email) {
if (!email || typeof email !== 'string') {
return { valid: false, error: 'Email is required' };
}
const trimmedEmail = email.trim().toLowerCase();
if (trimmedEmail.length > 254) {
return { valid: false, error: 'Email address too long' };
}
if (!emailRegex.test(trimmedEmail)) {
return { valid: false, error: 'Invalid email format' };
}
const [localPart, domain] = trimmedEmail.split('@');
if (localPart.length > 64) {
return { valid: false, error: 'Local part too long' };
}
return { valid: true, email: trimmedEmail };
}
åºæ¬çãªæ£èŠè¡šçŸæ€èšŒãè¶ ããŠ
æ£èŠè¡šçŸã¯æãããªãã©ãŒããããšã©ãŒããã£ããããŸããã远å ã®ãã§ãã¯ã«ããæ€èšŒç²ŸåºŠãåäžããŸãããããã«ã¯ãé£ç¶ãããããã®ãã§ãã¯ããããã¬ãã«ãã¡ã€ã³ã®é·ãã®æ€èšŒãäžè¬çãªã¿ã€ããã¹ãã¿ãŒã³ã®æ€åºãå«ãŸããŸãã
function enhancedSyntaxValidation(email) {
const basicResult = validateEmailSyntax(email);
if (!basicResult.valid) return basicResult;
const normalizedEmail = basicResult.email;
const [localPart, domain] = normalizedEmail.split('@');
// é£ç¶ãããããããã§ãã¯
if (localPart.includes('..') || domain.includes('..')) {
return { valid: false, error: 'Consecutive dots not allowed' };
}
// å
é /æ«å°Ÿã®ãããããã§ãã¯
if (localPart.startsWith('.') || localPart.endsWith('.')) {
return { valid: false, error: 'Local part cannot start or end with dot' };
}
// TLDãæ€èšŒ
const tld = domain.split('.').pop();
if (tld.length < 2 || tld.length > 63) {
return { valid: false, error: 'Invalid top-level domain' };
}
// æ°åã®ã¿ã®TLDããã§ãã¯(ç¡å¹)
if (/^\d+$/.test(tld)) {
return { valid: false, error: 'TLD cannot be numeric only' };
}
return { valid: true, email: normalizedEmail };
}
DNS ãš MX ã¬ã³ãŒãæ€èšŒ
æ§ææ€èšŒã®åŸãDNS æ€èšŒã¯æå¹ãª MX ã¬ã³ãŒãã確èªããããšã§ãã¡ãŒã«ãã¡ã€ã³ãã¡ãŒã«ãåä¿¡ã§ããããšãæ€èšŒããŸãã
MX ã¬ã³ãŒãã®çè§£
ã¡ãŒã«äº€æ(MX)ã¬ã³ãŒãã¯ããã¡ã€ã³ã®ã¡ãŒã«ãåãå ¥ãã責任ãããã¡ãŒã«ãµãŒããŒãæå®ãã DNS ã¬ã³ãŒãã§ããå MX ã¬ã³ãŒãã«ã¯åªå 床å€ãšãã¹ãåãå«ãŸããŠããããã¡ã€ã³ããã§ã€ã«ãªãŒããŒãæã€è€æ°ã®ã¡ãŒã«ãµãŒããŒãèšå®ã§ããããã«ãªã£ãŠããŸãã
user@example.com ã«ã¡ãŒã«ãéä¿¡ããå Žåãéä¿¡ãµãŒããŒã¯ example.com ã® MX ã¬ã³ãŒãã«ã€ã㊠DNS ãã¯ãšãªããå¿çããæé«åªå 床(æå°çªå·)ã®ã¡ãŒã«ãµãŒããŒã«æ¥ç¶ããŸãã
Node.js ã§ã® MX ã«ãã¯ã¢ããã®å®è£
Node.js 㯠dns ã¢ãžã¥ãŒã«ãéããŠçµã¿èŸŒã¿ã® DNS 解決ãæäŸããMX æ€èšŒãç°¡åã«å®è£ ã§ããŸãã
const dns = require('dns').promises;
async function validateMXRecords(domain) {
try {
const mxRecords = await dns.resolveMx(domain);
if (!mxRecords || mxRecords.length === 0) {
return {
valid: false,
error: 'No MX records found',
domain
};
}
// åªå
床ã§ãœãŒã(äœãæ¹ãé«åªå
床)
const sortedRecords = mxRecords.sort((a, b) => a.priority - b.priority);
return {
valid: true,
domain,
mxRecords: sortedRecords,
primaryMX: sortedRecords[0].exchange
};
} catch (error) {
if (error.code === 'ENOTFOUND' || error.code === 'ENODATA') {
return {
valid: false,
error: 'Domain does not exist or has no MX records',
domain
};
}
return {
valid: false,
error: `DNS lookup failed: ${error.message}`,
domain
};
}
}
async function validateEmailDomain(email) {
const domain = email.split('@')[1];
// ãŸãMXã¬ã³ãŒãã詊ã
const mxResult = await validateMXRecords(domain);
if (mxResult.valid) return mxResult;
// Aã¬ã³ãŒããã§ãã¯ã«ãã©ãŒã«ããã¯(äžéšã®ãã¡ã€ã³ã¯MXãªãã§ã¡ãŒã«ãåãå
¥ãã)
try {
const aRecords = await dns.resolve4(domain);
if (aRecords && aRecords.length > 0) {
return {
valid: true,
domain,
mxRecords: [],
fallbackToA: true,
aRecords
};
}
} catch (error) {
// Aã¬ã³ãŒãã®ã«ãã¯ã¢ããã倱æ
}
return mxResult;
}
DNS ãšããžã±ãŒã¹ã®åŠç
æ¬çªç°å¢ã®ã¡ãŒã«æ€èšŒã¯ãã¿ã€ã ã¢ãŠããäžæçãªé害ãç°åžžãªæ§æãæã€ãã¡ã€ã³ãªã©ãããŸããŸãª DNS ãšããžã±ãŒã¹ãåŠçããå¿ èŠããããŸãã
async function robustDNSValidation(email, options = {}) {
const { timeout = 5000, retries = 2 } = options;
const domain = email.split('@')[1];
for (let attempt = 0; attempt <= retries; attempt++) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const result = await validateEmailDomain(email);
clearTimeout(timeoutId);
return result;
} catch (error) {
if (attempt === retries) {
return {
valid: false,
error: 'DNS validation failed after retries',
domain,
temporary: true
};
}
// ææ°ããã¯ãªã
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 100)
);
}
}
}
SMTP æ€èšŒã®å®è£
SMTP æ€èšŒã¯ãåä¿¡è ã®ã¡ãŒã«ãµãŒããŒã«çŽæ¥ã¯ãšãªããŠã¡ãŒã«ããã¯ã¹ãååšããããšã確èªããããšã§ãæé«ã®ç²ŸåºŠãæäŸããŸãã
SMTP æ€èšŒã®ä»çµã¿
SMTP æ€èšŒã¯ãå®éã«ã¡ãã»ãŒãžãé ä¿¡ããã«ã¡ãŒã«ãéä¿¡ããæåã®ã¹ããããã·ãã¥ã¬ãŒãããŸããæ€èšŒããã»ã¹ã¯ãã¡ãŒã«ãµãŒããŒãžã®æ¥ç¶ã確ç«ããEHLO/HELO ã§èªå·±ç޹ä»ããMAIL FROM ã§éä¿¡è ã¢ãã¬ã¹ãæäŸããæ¬¡ã« RCPT TO ã§ã¿ãŒã²ããã¢ãã¬ã¹ãžã®éä¿¡ãèŠæ±ããŸãã
RCPT TO ãžã® ã¡ãŒã«ãµãŒããŒã®å¿çã¯ãã¡ãŒã«ããã¯ã¹ãååšãããã©ããã瀺ããŸãã250 å¿çã¯ã¢ãã¬ã¹ãæå¹ã§ããããšã確èªãã550 ã¯ãŠãŒã¶ãŒãååšããªãããšã瀺ããŸãããã ããçŸåšå€ãã®ãµãŒããŒããã£ãããªãŒã«ã®æ§æãã°ã¬ã€ãªã¹ãã䜿çšããŠããããã®ããã»ã¹ãè€éã«ããŠããŸãã
Node.js ã§ã®åºæ¬ç㪠SMTP æ€èšŒ
const net = require('net');
class SMTPVerifier {
constructor(options = {}) {
this.timeout = options.timeout || 10000;
this.fromEmail = options.fromEmail || 'verify@example.com';
this.fromDomain = options.fromDomain || 'example.com';
}
async verify(email, mxHost) {
return new Promise((resolve) => {
const socket = new net.Socket();
let step = 0;
let response = '';
const cleanup = () => {
socket.destroy();
};
socket.setTimeout(this.timeout);
socket.on('timeout', () => {
cleanup();
resolve({ valid: false, error: 'Connection timeout' });
});
socket.on('error', (error) => {
cleanup();
resolve({ valid: false, error: error.message });
});
socket.on('data', (data) => {
response = data.toString();
const code = parseInt(response.substring(0, 3));
switch (step) {
case 0: // æ¥ç¶ãæšæ¶ãåä¿¡
if (code === 220) {
socket.write(`EHLO ${this.fromDomain}\r\n`);
step = 1;
} else {
cleanup();
resolve({ valid: false, error: 'Invalid greeting' });
}
break;
case 1: // EHLOå¿ç
if (code === 250) {
socket.write(`MAIL FROM:<${this.fromEmail}>\r\n`);
step = 2;
} else {
cleanup();
resolve({ valid: false, error: 'EHLO rejected' });
}
break;
case 2: // MAIL FROMå¿ç
if (code === 250) {
socket.write(`RCPT TO:<${email}>\r\n`);
step = 3;
} else {
cleanup();
resolve({ valid: false, error: 'MAIL FROM rejected' });
}
break;
case 3: // RCPT TOå¿ç - æ€èšŒçµæ
socket.write('QUIT\r\n');
cleanup();
if (code === 250) {
resolve({ valid: true, email });
} else if (code === 550 || code === 551 || code === 552 || code === 553) {
resolve({ valid: false, error: 'Mailbox does not exist', code });
} else if (code === 450 || code === 451 || code === 452) {
resolve({ valid: false, error: 'Temporary failure', temporary: true, code });
} else {
resolve({ valid: false, error: `Unknown response: ${code}`, code });
}
break;
}
});
socket.connect(25, mxHost);
});
}
}
SMTP ã®èª²é¡ãžã®å¯ŸåŠ
å®äžçã® SMTP æ€èšŒã¯ãã°ã¬ã€ãªã¹ããã¬ãŒãå¶éããã£ãããªãŒã«ãã¡ã€ã³ãªã©ã倿°ã®èª²é¡ã«çŽé¢ããŸããéçºè ã¯ããããã®ç¶æ³ãåŠçããããã®æŠç¥ãå®è£ ããå¿ èŠããããŸãã
async function comprehensiveSMTPVerification(email, mxRecords) {
const verifier = new SMTPVerifier({
fromEmail: 'verify@yourdomain.com',
fromDomain: 'yourdomain.com',
timeout: 15000
});
// åªå
é äœã«åŸã£ãŠåMXãµãŒããŒã詊ã
for (const mx of mxRecords) {
const result = await verifier.verify(email, mx.exchange);
// 決å®çãªçããåŸãããå Žåããããè¿ã
if (result.valid || (!result.temporary && result.code === 550)) {
return result;
}
// äžæçãªéå®³ãæ¥ç¶ã®åé¡ã®å Žåãæ¬¡ã®ãµãŒããŒã詊ã
if (result.temporary || result.error.includes('timeout')) {
continue;
}
// ãã®ä»ã®ãšã©ãŒã®å Žåãçµæãè¿ã
return result;
}
return {
valid: false,
error: 'All MX servers failed',
temporary: true
};
}
ã¡ãŒã«æ€èšŒ API ã®äœ¿çš
ã«ã¹ã¿ã æ€èšŒã®æ§ç¯ã¯æè²çã§ãããæ¬çªã¢ããªã±ãŒã·ã§ã³ã¯ãããã§ãã·ã§ãã«ãªã¡ãŒã«é ä¿¡æ§ã確ä¿ãããããBillionVerify ã®ãããªã¡ãŒã«æ€èšŒ API ã䜿çšããããšã§æ©æµãåããããšããããããŸãã
ã¡ãŒã«æ€èšŒ API ã䜿çšããçç±
ãããã§ãã·ã§ãã«ãªã¡ãŒã«æ€èšŒãµãŒãã¹ã¯ãã«ã¹ã¿ã å®è£ ã«æ¯ã¹ãŠããã€ãã®å©ç¹ãæäŸããŸããæ¢ç¥ã®äœ¿ãæšãŠã¡ãŒã«ãããã€ããŒããã£ãããªãŒã«ãã¡ã€ã³ãã¹ãã ãã©ããã®åºç¯ãªããŒã¿ããŒã¹ãç¶æããŠããŸãããŸãããããã¯ãããããšãªã倧éã® SMTP æ€èšŒã«å¿ èŠãªã€ã³ãã©ã¹ãã©ã¯ãã£ã管çããŸãã
BillionVerify ã®ã¡ãŒã«æ€èšŒ API ã¯ãæ§æãã§ãã¯ãDNS æ€èšŒãSMTP æ€èšŒãäœ¿ãæšãŠã¡ãŒã«æ€åºãé ä¿¡å¯èœæ§ã¹ã³ã¢ãªã³ã°ãå«ãå æ¬çãªæ€èšŒããã·ã³ãã«ãª REST API ãéããŠæäŸããŸãã
BillionVerify API ã®çµ±å
const axios = require('axios');
class BillionVerifyClient {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseURL = 'https://api.billionverify.com/v1';
}
async verifySingle(email) {
try {
const response = await axios.get(`${this.baseURL}/verify`, {
params: { email },
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
}
});
return {
success: true,
data: response.data
};
} catch (error) {
return {
success: false,
error: error.response?.data?.message || error.message
};
}
}
async verifyBatch(emails) {
try {
const response = await axios.post(`${this.baseURL}/verify/batch`, {
emails
}, {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
}
});
return {
success: true,
data: response.data
};
} catch (error) {
return {
success: false,
error: error.response?.data?.message || error.message
};
}
}
}
// 䜿çšäŸ
async function validateUserEmail(email) {
const client = new BillionVerifyClient(process.env.BILLIONVERIFY_API_KEY);
const result = await client.verifySingle(email);
if (!result.success) {
console.error('Verification failed:', result.error);
return { valid: false, error: 'Verification service unavailable' };
}
const { data } = result;
return {
valid: data.deliverable,
email: data.email,
status: data.status,
isDisposable: data.is_disposable,
isCatchAll: data.is_catch_all,
score: data.quality_score
};
}
ãŠã§ãã¢ããªã±ãŒã·ã§ã³ã§ã®ãªã¢ã«ã¿ã€ã æ€èšŒ
ãŠã§ãã¢ããªã±ãŒã·ã§ã³ã§ãªã¢ã«ã¿ã€ã ã¡ãŒã«æ€èšŒãå®è£ ããã«ã¯ããŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ãšããã©ãŒãã³ã¹ãæ éã«èæ ®ããå¿ èŠããããŸãã
ããã³ããšã³ãæ€èšŒæŠç¥
ããã³ããšã³ãæ€èšŒã¯ãæãããªãšã©ãŒã«å¯ŸããŠå³åº§ã®ãã£ãŒãããã¯ãæäŸããå æ¬çãªæ€èšŒãããã¯ãšã³ãã«å»¶æããå¿ èŠããããŸãããã®ã¢ãããŒãã¯ããŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ãšã»ãã¥ãªãã£ã®ãã©ã³ã¹ãåããŸãã
// ãããŠã³ã¹ä»ãããã³ããšã³ãã¡ãŒã«æ€èšŒ
class EmailValidator {
constructor(options = {}) {
this.debounceMs = options.debounceMs || 500;
this.onValidating = options.onValidating || (() => {});
this.onResult = options.onResult || (() => {});
this.pendingRequest = null;
this.debounceTimer = null;
}
validateSyntax(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
async validate(email) {
// ä¿çäžã®ãªã¯ãšã¹ããã¯ãªã¢
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
// å³åº§ã®æ§æãã§ãã¯
if (!this.validateSyntax(email)) {
this.onResult({
valid: false,
error: 'Please enter a valid email address'
});
return;
}
// APIåŒã³åºãã®ãããŠã³ã¹
this.debounceTimer = setTimeout(async () => {
this.onValidating(true);
try {
const response = await fetch('/api/verify-email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email })
});
const result = await response.json();
this.onResult(result);
} catch (error) {
this.onResult({
valid: false,
error: 'Unable to verify email'
});
} finally {
this.onValidating(false);
}
}, this.debounceMs);
}
}
// Reactã³ã³ããŒãã³ãã®äŸ
function EmailInput() {
const [email, setEmail] = useState('');
const [status, setStatus] = useState({ checking: false, result: null });
const validator = useMemo(() => new EmailValidator({
onValidating: (checking) => setStatus(s => ({ ...s, checking })),
onResult: (result) => setStatus(s => ({ ...s, result }))
}), []);
const handleChange = (e) => {
const value = e.target.value;
setEmail(value);
if (value) validator.validate(value);
};
return (
<div className="email-input">
<input
type="email"
value={email}
onChange={handleChange}
placeholder="Enter your email"
/>
{status.checking && <span className="loading">Verifying...</span>}
{status.result && (
<span className={status.result.valid ? 'valid' : 'invalid'}>
{status.result.valid ? 'â Valid email' : status.result.error}
</span>
)}
</div>
);
}
ããã¯ãšã³ã API ãšã³ããã€ã³ã
ããã¯ãšã³ã API ãšã³ããã€ã³ãã¯ãã¬ãŒãå¶éãéããŠæªçšããä¿è·ããªãããå æ¬çãªæ€èšŒãå®è£ ããå¿ èŠããããŸãã
const express = require('express');
const rateLimit = require('express-rate-limit');
const app = express();
// æ€èšŒãšã³ããã€ã³ãã®ã¬ãŒãå¶é
const verifyLimiter = rateLimit({
windowMs: 60 * 1000, // 1å
max: 10, // IPããã1åããã10ãªã¯ãšã¹ã
message: { error: 'Too many verification requests' }
});
app.post('/api/verify-email', verifyLimiter, async (req, res) => {
const { email } = req.body;
if (!email) {
return res.status(400).json({ valid: false, error: 'Email required' });
}
try {
// ã¬ã€ã€ãŒ1:æ§ææ€èšŒ
const syntaxResult = enhancedSyntaxValidation(email);
if (!syntaxResult.valid) {
return res.json(syntaxResult);
}
// ã¬ã€ã€ãŒ2:DNSæ€èšŒ
const dnsResult = await robustDNSValidation(syntaxResult.email);
if (!dnsResult.valid) {
return res.json(dnsResult);
}
// ã¬ã€ã€ãŒ3:APIããŒã¹ã®å
æ¬çæ€èšŒ
const apiResult = await validateUserEmail(syntaxResult.email);
res.json(apiResult);
} catch (error) {
console.error('Verification error:', error);
res.status(500).json({ valid: false, error: 'Verification failed' });
}
});
äœ¿ãæšãŠã¡ãŒã«ãšäžæã¡ãŒã«ã®æ€åº
äœ¿ãæšãŠã¡ãŒã«ã¢ãã¬ã¹ã¯ãæ¬ç©ã®ãŠãŒã¶ãŒãšã³ã²ãŒãžã¡ã³ããå¿ èŠãšããã¢ããªã±ãŒã·ã§ã³ã«ãšã£ãŠé倧ãªèª²é¡ããããããŸãããããã®ã¢ãã¬ã¹ãæ€åºããŠãããã¯ããããšã¯ããªã¹ãã®å質ãç¶æããããã«äžå¯æ¬ ã§ãã
äœ¿ãæšãŠã¡ãŒã«ã®çè§£
Guerrilla Mailã10MinuteMailãMailinator ãªã©ã®äœ¿ãæšãŠã¡ãŒã«ãµãŒãã¹ã¯ããŠãŒã¶ãŒãç»é²ãªãã§å³åº§ã«äœæã§ããäžæçãªã¢ãã¬ã¹ãæäŸããŸãããããã®ãµãŒãã¹ã«ã¯æ£åœãªçšéããããŸãããå€ãã®å Žåãç»é²èŠä»¶ãåé¿ããããåœã¢ã«ãŠã³ããäœæãããããããã«äœ¿çšãããŸãã
äœ¿ãæšãŠã¡ãŒã«æ€åºåšã®æ§ç¯
class DisposableEmailDetector {
constructor() {
// äžè¬çãªäœ¿ãæšãŠã¡ãŒã«ãã¡ã€ã³
this.knownDisposable = new Set([
'guerrillamail.com', 'guerrillamail.org',
'10minutemail.com', '10minutemail.net',
'mailinator.com', 'mailinator.net',
'tempmail.com', 'tempmail.net',
'throwaway.email', 'throwawaymail.com',
'fakeinbox.com', 'trashmail.com',
'getnada.com', 'temp-mail.org',
'mohmal.com', 'emailondeck.com'
// ããã«æ¢ç¥ã®äœ¿ãæšãŠãã¡ã€ã³ã远å
]);
// äœ¿ãæšãŠãµãŒãã¹ã瀺ãããšãå€ããã¿ãŒã³
this.suspiciousPatterns = [
/^temp/i,
/^trash/i,
/^throw/i,
/^fake/i,
/^disposable/i,
/\d{2,}mail/i,
/minutemail/i
];
}
isDisposable(email) {
const domain = email.split('@')[1].toLowerCase();
// æ¢ç¥ã®äœ¿ãæšãŠãã¡ã€ã³ããã§ãã¯
if (this.knownDisposable.has(domain)) {
return { isDisposable: true, reason: 'Known disposable domain' };
}
// çããããã¿ãŒã³ããã§ãã¯
for (const pattern of this.suspiciousPatterns) {
if (pattern.test(domain)) {
return { isDisposable: true, reason: 'Suspicious domain pattern' };
}
}
return { isDisposable: false };
}
async updateDisposableList() {
// ç¶æãããŠãããœãŒã¹ããæŽæ°ããããªã¹ããååŸ
try {
const response = await fetch(
'https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/master/disposable_email_blocklist.conf'
);
const text = await response.text();
const domains = text.split('\n').filter(d => d.trim());
domains.forEach(domain => this.knownDisposable.add(domain.toLowerCase()));
return { success: true, count: this.knownDisposable.size };
} catch (error) {
return { success: false, error: error.message };
}
}
}
ããã©ãŒãã³ã¹æé©åæŠç¥
ã¡ãŒã«æ€èšŒã¯ãæ éã«å®è£ ããªããšã¢ããªã±ãŒã·ã§ã³ã®ããã©ãŒãã³ã¹ã«åœ±é¿ãäžããå¯èœæ§ããããŸãããããã®æé©åæŠç¥ã¯ãé«éãªå¿çæéãç¶æããã®ã«åœ¹ç«ã¡ãŸãã
æ€èšŒçµæã®ãã£ãã·ã¥
ãã£ãã·ã¥ã¯ãåé·ãªæ€èšŒãªã¯ãšã¹ããåæžããç¹°ãè¿ãã®æ€èšŒã®å¿çæéãæ¹åããŸãã
const NodeCache = require('node-cache');
class CachedEmailVerifier {
constructor(options = {}) {
this.cache = new NodeCache({
stdTTL: options.ttl || 3600, // ããã©ã«ã1æé
checkperiod: options.checkperiod || 600
});
this.verifier = options.verifier;
}
async verify(email) {
const normalizedEmail = email.toLowerCase().trim();
const cacheKey = `email:${normalizedEmail}`;
// ãŸããã£ãã·ã¥ããã§ãã¯
const cached = this.cache.get(cacheKey);
if (cached) {
return { ...cached, fromCache: true };
}
// æ€èšŒãå®è¡
const result = await this.verifier.verify(normalizedEmail);
// çµæããã£ãã·ã¥(äžæçãªé害ã¯ãã£ãã·ã¥ããªã)
if (!result.temporary) {
this.cache.set(cacheKey, result);
}
return result;
}
invalidate(email) {
const normalizedEmail = email.toLowerCase().trim();
this.cache.del(`email:${normalizedEmail}`);
}
getStats() {
return this.cache.getStats();
}
}
ãªã¯ãšã¹ããã¥ãŒã€ã³ã°ã®å®è£
倧éã®ã¢ããªã±ãŒã·ã§ã³ã®å Žåããªã¯ãšã¹ããã¥ãŒã€ã³ã°ã¯æ€èšŒãµãŒãã¹ãå§åããããšãé²ããå ¬å¹³ãªãªãœãŒã¹é åãä¿èšŒããŸãã
const Queue = require('bull');
const verificationQueue = new Queue('email-verification', {
redis: { host: 'localhost', port: 6379 },
defaultJobOptions: {
attempts: 3,
backoff: {
type: 'exponential',
delay: 1000
}
}
});
// æ€èšŒãžã§ããåŠç
verificationQueue.process(async (job) => {
const { email, userId } = job.data;
const result = await comprehensiveEmailVerification(email);
// çµæãããŒã¿ããŒã¹ã«ä¿å
await updateUserEmailStatus(userId, result);
return result;
});
// æ€èšŒãªã¯ãšã¹ãããã¥ãŒã«å
¥ãã
async function queueEmailVerification(email, userId) {
const job = await verificationQueue.add({
email,
userId
}, {
priority: 1,
delay: 0
});
return job.id;
}
ãšã©ãŒåŠçãšãã®ã³ã°
å ç¢ãªãšã©ãŒåŠçãšå æ¬çãªãã®ã³ã°ã¯ãä¿¡é Œæ§ã®é«ãã¡ãŒã«æ€èšŒã·ã¹ãã ãç¶æããããã«äžå¯æ¬ ã§ãã
å æ¬çãªãšã©ãŒåŠçã®å®è£
class EmailVerificationError extends Error {
constructor(message, code, details = {}) {
super(message);
this.name = 'EmailVerificationError';
this.code = code;
this.details = details;
this.timestamp = new Date().toISOString();
}
}
async function safeEmailVerification(email) {
const startTime = Date.now();
try {
// å
¥åãæ€èšŒ
if (!email || typeof email !== 'string') {
throw new EmailVerificationError(
'Invalid email input',
'INVALID_INPUT',
{ received: typeof email }
);
}
const result = await comprehensiveEmailVerification(email);
// æåããæ€èšŒããã°ã«èšé²
logger.info('Email verification completed', {
email: maskEmail(email),
valid: result.valid,
duration: Date.now() - startTime
});
return result;
} catch (error) {
// ã³ã³ããã¹ããå«ããšã©ãŒããã°ã«èšé²
logger.error('Email verification failed', {
email: maskEmail(email),
error: error.message,
code: error.code,
duration: Date.now() - startTime,
stack: error.stack
});
// å®å
šãªãšã©ãŒå¿çãè¿ã
return {
valid: false,
error: 'Verification failed',
errorCode: error.code || 'UNKNOWN_ERROR',
temporary: true
};
}
}
function maskEmail(email) {
const [local, domain] = email.split('@');
const maskedLocal = local.charAt(0) + '***' + local.charAt(local.length - 1);
return `${maskedLocal}@${domain}`;
}
ã»ãã¥ãªãã£ã«é¢ããèæ ®äºé
ã¡ãŒã«æ€èšŒã·ã¹ãã ã¯ãæªçšã鲿¢ãããŠãŒã¶ãŒããŒã¿ãä¿è·ããããã«ã»ãã¥ãªãã£ã念é ã«çœ®ããŠèšèšããå¿ èŠããããŸãã
åææ»æã®é²æ¢
æ»æè ã¯ãã¡ãŒã«æ€èšŒãšã³ããã€ã³ãã䜿çšããŠæå¹ãªã¡ãŒã«ã¢ãã¬ã¹ãåæããå¯èœæ§ããããŸãããã®æ»æãã¯ãã«ã«å¯Ÿããé²åŸ¡ãå®è£ ããŸãã
const crypto = require('crypto');
function secureVerificationResponse(result, options = {}) {
const { hideDetails = true } = options;
// ã¿ã€ãã³ã°æ»æãé²ãããã«äžè²«ããå¿çã¿ã€ãã³ã°ã远å
const minResponseTime = 200;
const elapsed = Date.now() - result.startTime;
const delay = Math.max(0, minResponseTime - elapsed);
return new Promise(resolve => {
setTimeout(() => {
if (hideDetails && !result.valid) {
// ã¡ãŒã«ãååšããããã¡ã€ã³ãç¡å¹ããæããã«ããªã
resolve({
valid: false,
message: 'Unable to verify email address'
});
} else {
resolve(result);
}
}, delay);
});
}
ã¬ãŒãå¶éãšæªçšé²æ¢
æ€èšŒãšã³ããã€ã³ãã®æªçšãé²ãããã«å æ¬çãªã¬ãŒãå¶éãå®è£ ããŸãã
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const verificationRateLimiter = rateLimit({
store: new RedisStore({
client: redisClient,
prefix: 'rl:verify:'
}),
windowMs: 60 * 1000, // 1å
max: 5, // 1åããã5ãªã¯ãšã¹ã
keyGenerator: (req) => {
// èªèšŒæžã¿ã®å Žåã¯IPãšãŠãŒã¶ãŒIDãçµã¿åããã
const userId = req.user?.id || 'anonymous';
return `${req.ip}:${userId}`;
},
handler: (req, res) => {
res.status(429).json({
error: 'Too many verification requests',
retryAfter: Math.ceil(req.rateLimit.resetTime / 1000)
});
}
});
ã¡ãŒã«æ€èšŒã·ã¹ãã ã®ãã¹ã
å æ¬çãªãã¹ãã«ãããã¡ãŒã«æ€èšŒã·ã¹ãã ããã¹ãŠã®ã·ããªãªã§æ£ããæ©èœããããšãä¿èšŒãããŸãã
æ€èšŒé¢æ°ã®åäœãã¹ã
const { expect } = require('chai');
describe('Email Syntax Validation', () => {
it('should accept valid email addresses', () => {
const validEmails = [
'user@example.com',
'user.name@example.com',
'user+tag@example.com',
'user@subdomain.example.com'
];
validEmails.forEach(email => {
const result = validateEmailSyntax(email);
expect(result.valid).to.be.true;
});
});
it('should reject invalid email addresses', () => {
const invalidEmails = [
'invalid',
'@example.com',
'user@',
'user@@example.com',
'user@example',
'user@.com'
];
invalidEmails.forEach(email => {
const result = validateEmailSyntax(email);
expect(result.valid).to.be.false;
});
});
it('should handle edge cases', () => {
expect(validateEmailSyntax('')).to.have.property('valid', false);
expect(validateEmailSyntax(null)).to.have.property('valid', false);
expect(validateEmailSyntax(undefined)).to.have.property('valid', false);
});
});
ãŸãšã
éçºè ãšããŠã¡ãŒã«æ€èšŒãå®è£ ããã«ã¯ãåºæ¬çãªæ§æãã§ãã¯ããé«åºŠãª SMTP æ€èšŒãŸã§ãè€æ°ã®æ€èšŒã¬ã€ã€ãŒãçè§£ããå¿ èŠããããŸããããŒã«ã«æ€èšŒãDNS ã«ãã¯ã¢ãããBillionVerify ã®ãããªãããã§ãã·ã§ãã«ãªæ€èšŒ API ãçµã¿åãããããšã§ãéçºè ã¯åªãããŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ãæäŸããªãããé«ãããŒã¿å質ãç¶æããå ç¢ãªã·ã¹ãã ãæ§ç¯ã§ããŸãã
ã¡ãŒã«æ€èšŒå®è£ ãæåãããããã®éèŠãªååã«ã¯ãå æ¬çãªã«ãã¬ããžã®ããã®è€æ°ã®æ€èšŒã¬ã€ã€ãŒã®äœ¿çšãããã©ãŒãã³ã¹ãšã»ãã¥ãªãã£ã®ããã®é©åãªãã£ãã·ã¥ãšã¬ãŒãå¶éã®å®è£ ããšããžã±ãŒã¹ãšãšã©ãŒã®é©åãªåŠçãæ€èšŒç²ŸåºŠã®ç¶ç¶çãªç£èŠãšæ¹åãå«ãŸããŸãã
ã«ã¹ã¿ã æ€èšŒããžãã¯ãå®è£ ãããããããã§ãã·ã§ãã«ãª API ãæŽ»çšãããã«ãããããããã®ã¬ã€ãã§ã«ããŒãããŠããæè¡ã¯ãã¢ããªã±ãŒã·ã§ã³ãšãŠãŒã¶ãŒãä¿è·ããªãããæé«æ°Žæºã®é ä¿¡å¯èœæ§ãšãšã³ã²ãŒãžã¡ã³ããç¶æããã¡ãŒã«æ€èšŒã·ã¹ãã ãæ§ç¯ããããã®åºç€ãæäŸããŸãã
ä»ãã BillionVerify ã®éçºè åã API ã§ã¢ããªã±ãŒã·ã§ã³ã«ã¡ãŒã«æ€èšŒã®å®è£ ãéå§ããŸããããBillionVerify ã«ãµã€ã³ã¢ããããŠãç¡æã®æ€èšŒã¯ã¬ãžãããšå æ¬çãªããã¥ã¡ã³ããå ¥æããŠãã ããã