์ด๋ฉ์ผ ์ฃผ์๋ฅผ ์์งํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ ๋๋ ๋ฐ์ดํฐ ํ์ง์ ์ ์งํ๊ณ ๋ฐ์ ์ ํํ์ ๋ณดํธํ๊ธฐ ์ํด ๊ฐ๋ ฅํ ์ด๋ฉ์ผ ๊ฒ์ฆ์ด ํ์ํฉ๋๋ค. Node.js ๊ฐ๋ฐ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ด๋ฉ์ผ ๊ฒ์ฆ ์๋น์ค๋ฅผ ํตํฉํ ์ ์๋ ๊ฐ๋ ฅํ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ์ข ํฉ ํํ ๋ฆฌ์ผ์ ๊ธฐ๋ณธ ์ค์ ๋ถํฐ ํ๋ก๋์ ์์ค์ ๊ตฌํ๊น์ง Node.js ์ด๋ฉ์ผ ๊ฒ์ฆ API ํตํฉ์ ๊ตฌํํ๋ ๊ณผ์ ์ ์๋ดํฉ๋๋ค.
Node.js ์ด๋ฉ์ผ ๊ฒ์ฆ ํตํฉ์ ์ ํํ๋ ์ด์
Node.js๋ ํ๋์ ์ธ ์น ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ์ถ์ ์ํ ์ ํธ ๋ฐํ์์ด ๋์์ผ๋ฉฐ, ๋น๋๊ธฐ ํน์ฑ์ผ๋ก ์ธํด ์ด๋ฉ์ผ ๊ฒ์ฆ๊ณผ ๊ฐ์ API ํตํฉ์ ํนํ ์ ํฉํฉ๋๋ค. ์ฌ์ฉ์๊ฐ ์์์ ํตํด ์ด๋ฉ์ผ ์ฃผ์๋ฅผ ์ ์ถํ ๋, ์ฌ์ฉ์ ๊ฒฝํ์ ์ ํ์ํค์ง ์๋ ๋น ๋ฅด๊ณ ๋ ผ๋ธ๋กํน ๊ฒ์ฆ์ด ํ์ํฉ๋๋ค. Node.js๋ ์ฌ๋ฌ ๋์ API ์์ฒญ์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๋ฐ ๋ฐ์ด๋ ์ค์๊ฐ ๋จ์ผ ์ด๋ฉ์ผ ์ ํจ์ฑ ๊ฒ์ฌ์ ์ผ๊ด ์ฒ๋ฆฌ ์๋๋ฆฌ์ค ๋ชจ๋์ ์ด์์ ์ ๋๋ค.
npm ์ํ๊ณ๋ API ํตํฉ์ ๊ฐ์ํํ๋ ์ฐ์ํ HTTP ํด๋ผ์ด์ธํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ ๊ณตํฉ๋๋ค. ๋ด์ฅ fetch API, axios ๋๋ node-fetch๋ฅผ ์ ํธํ๋ , Node.js ์ด๋ฉ์ผ ์ ํจ์ฑ ๊ฒ์ฌ๊ธฐ๋ฅผ ๊ตฌํํ๋ ๋ฐ ํ์ํ ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋๋ ์ต์ํํ๋ฉด์ ์ฌ์ฉ์ ์ ์๋ฅผ ์ํ ์ต๋์ ์ ์ฐ์ฑ์ ์ ๊ณตํฉ๋๋ค.
Node.js ํ๋ก์ ํธ ์ค์
์์กด์ฑ ์ค์น
์ด๋ฉ์ผ ๊ฒ์ฆ ๊ตฌํ์ ์์ํ๊ธฐ ์ ์ ๊ฐ๋ฐ ํ๊ฒฝ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌ์ฑ๋์๋์ง ํ์ธํ์ธ์. ๋ค์ดํฐ๋ธ fetch API๋ฅผ ํ์ฉํ๋ ค๋ฉด Node.js ๋ฒ์ 18 ์ด์์ด ํ์ํ์ง๋ง, ์ด์ ๋ฒ์ ์์๋ node-fetch๋ฅผ ํด๋ฆฌํ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ ํ๋ก์ ํธ ๋๋ ํ ๋ฆฌ๋ฅผ ์์ฑํ๊ณ npm์ผ๋ก ์ด๊ธฐํํ์ธ์. package.json์๋ HTTP ์์ฒญ ๋ฐ ํ๊ฒฝ ๋ณ์ ๊ด๋ฆฌ๋ฅผ ์ํ ํ์ ์ข ์์ฑ์ด ํฌํจ๋์ด์ผ ํฉ๋๋ค. dotenv ํจํค์ง๋ ์์ค ์ฝ๋์ ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ํ๋์ฝ๋ฉํ๋ ๋์ ํ๊ฒฝ ํ์ผ์์ ๋ก๋ํ์ฌ API ์๊ฒฉ ์ฆ๋ช ์ ์์ ํ๊ฒ ์ ์งํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
// package.json
{
"name": "email-verification-demo",
"version": "1.0.0",
"type": "module",
"dependencies": {
"dotenv": "^16.3.1"
}
}
ํ๊ฒฝ ๋ณ์ ๊ตฌ์ฑ
BillionVerify API ํค๋ฅผ ํ๊ฒฝ ํ์ผ์ ์ ์ฅํ์ธ์. API ํค๋ฅผ ๋ฒ์ ๊ด๋ฆฌ์ ์ปค๋ฐํ์ง ๋ง์ธ์. .env ํ์ผ์ ์๊ฒฉ ์ฆ๋ช ์ ์ฝ๋๋ฒ ์ด์ค์ ๋ถ๋ฆฌํ์ฌ ๋ชจ๋ ์ด๋ฉ์ผ ๊ฒ์ฆ ์๋น์ค๊ฐ ๊ถ์ฅํ๋ ๋ณด์ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ฐ๋ฆ ๋๋ค.
# .env BILLIONVERIFY_API_KEY=your_api_key_here
๋จ์ผ ์ด๋ฉ์ผ ์ฃผ์ ํ์ธ ๊ตฌํ
์ฒซ ๋ฒ์งธ API ํธ์ถํ๊ธฐ
๋ชจ๋ ์ด๋ฉ์ผ ๊ฒ์ฆ ํตํฉ์ ๊ธฐ์ด๋ ๊ฐ๋ณ ์ด๋ฉ์ผ ์ฃผ์๋ฅผ ํ์ธํ๋ ๊ธฐ๋ฅ์ ๋๋ค. ์ด ๊ธฐ๋ฅ์ ์ฌ์ฉ์ ๋ฑ๋ก, ์ฐ๋ฝ์ฒ ์์ ์ ์ถ ๋ฐ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ด ํ์ํ ๋ชจ๋ ์๋๋ฆฌ์ค์์ ์ค์๊ฐ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ง์ํฉ๋๋ค.
BillionVerify ์ด๋ฉ์ผ ๊ฒ์ฆ API๋ ์์ฒญ ๋ณธ๋ฌธ์ ์ด๋ฉ์ผ ์ฃผ์๊ฐ ํฌํจ๋ POST ์์ฒญ์ ์๋ฝํฉ๋๋ค. ์๋ต์๋ ์ ํจ์ฑ ์ํ, ์ ๋ฌ ๊ฐ๋ฅ์ฑ ํ๊ฐ, ์ผํ์ฉ ์ด๋ฉ์ผ, ์ญํ ๊ธฐ๋ฐ ์ฃผ์ ๋ฐ ์บ์น์ฌ ๋๋ฉ์ธ์ ๋ํ ์์ธํ ํ์ธ์ ํฌํจํ ํฌ๊ด์ ์ธ ๊ฒ์ฆ ๊ฒฐ๊ณผ๊ฐ ํฌํจ๋ฉ๋๋ค.
// verify-email.js
import 'dotenv/config';
const API_BASE_URL = 'https://api.billionverify.com/v1';
const API_KEY = process.env.BILLIONVERIFY_API_KEY;
async function verifyEmail(email) {
const response = await fetch(`${API_BASE_URL}/verify`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ email })
});
if (!response.ok) {
throw new Error(`Verification failed: ${response.status}`);
}
return response.json();
}
// Usage example
const result = await verifyEmail('user@example.com');
console.log(result);
์๋ต ํ๋ ์ดํดํ๊ธฐ
๊ฒ์ฆ ์๋ต์ ๊ฐ ์ด๋ฉ์ผ ์ฃผ์์ ๋ํ ์คํ ๊ฐ๋ฅํ ์ ๋ณด๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด๋ฌํ ์๋ต ํ๋๋ฅผ ์ดํดํ๋ฉด ์ด๋ฉ์ผ ์ฃผ์๋ฅผ ์์คํ ์ ์๋ฝํ ์ง ์ฌ๋ถ์ ๋ํ ์ ๋ณด์ ์ ๊ฐํ ๊ฒฐ์ ์ ๋ด๋ฆด ์ ์์ต๋๋ค.
| ํ๋ | ์ค๋ช | ์ฌ์ฉ ์ฌ๋ก |
|---|---|---|
| is_valid | ์ ๋ฐ์ ์ธ ์ ํจ์ฑ ํ๊ฐ | ์๋ฝ/๊ฑฐ๋ถ ๊ฒฐ์ |
| is_deliverable | ์ด๋ฉ์ผ ์์ ๊ฐ๋ฅ ์ฌ๋ถ | ์ด๋ฉ์ผ ์บ ํ์ธ ์ ๊ฒฉ์ฑ |
| is_disposable | ์์ ์ด๋ฉ์ผ ์๋น์ค | ์ฌ๊ธฐ ๋ฐฉ์ง |
| is_role_based | ์ผ๋ฐ ์ฃผ์ (info@, support@) | B2B ํ๊ฒํ |
| is_catch_all | ๋๋ฉ์ธ์ด ๋ชจ๋ ์ฃผ์ ์๋ฝ | ์ํ ํ๊ฐ |
| risk_score | 0-100 ์ํ ๋ฑ๊ธ | ์ธ๋ฐํ ํํฐ๋ง |
์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ด๋ฉ์ผ ์ ํจ์ฑ ๊ฒ์ฌ๊ธฐ ํด๋์ค ๊ตฌ์ถ
ํด๋์ค ์ํคํ ์ฒ
ํ๋ก๋์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ด๋ฉ์ผ ๊ฒ์ฆ ๋ก์ง์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ํด๋์ค๋ก ์บก์ํํ๋ฉด ์ด์ ์ด ์์ต๋๋ค. ์ด ์ ๊ทผ ๋ฐฉ์์ ์ผ๊ด๋ ์ค๋ฅ ์ฒ๋ฆฌ, ์๋ ์ฌ์๋ ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๋จธ์ง ๋ถ๋ถ์ด ์ฌ์ฉํ ์ ์๋ ๊น๋ํ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํฉ๋๋ค.
EmailValidator ํด๋์ค๋ HTTP ์ธ๋ถ ์ฌํญ์ ์ถ์ํํ๊ณ ์ผ๋ฐ์ ์ธ ๊ฒ์ฆ ์๋๋ฆฌ์ค๋ฅผ ์ํ ๋ฉ์๋๋ฅผ ์ ๊ณตํฉ๋๋ค. API ์ธ์ฆ, ์์ฒญ ํ์ ์ง์ ๋ฐ ์๋ต ๊ตฌ๋ฌธ ๋ถ์์ ์ฒ๋ฆฌํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋๊ฐ API ๋ฉ์ปค๋์ฆ ๋์ ๋น์ฆ๋์ค ๋ก์ง์ ์ง์คํ ์ ์๋๋ก ํฉ๋๋ค.
// EmailValidator.js
import 'dotenv/config';
class EmailValidator {
constructor(apiKey = process.env.BILLIONVERIFY_API_KEY) {
this.apiKey = apiKey;
this.baseUrl = 'https://api.billionverify.com/v1';
this.maxRetries = 3;
this.retryDelay = 1000;
}
async verify(email) {
let lastError;
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
const response = await fetch(`${this.baseUrl}/verify`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ email })
});
if (response.status === 429) {
// Rate limited - wait and retry
await this.sleep(this.retryDelay * attempt);
continue;
}
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return await response.json();
} catch (error) {
lastError = error;
if (attempt < this.maxRetries) {
await this.sleep(this.retryDelay * attempt);
}
}
}
throw lastError;
}
async isValid(email) {
const result = await this.verify(email);
return result.is_valid && result.is_deliverable;
}
async isHighRisk(email) {
const result = await this.verify(email);
return result.risk_score > 70 || result.is_disposable;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
export default EmailValidator;
์๋ ์ฌ์๋ ๋ก์ง
์ด ํด๋์ค๋ ์คํจํ ์์ฒญ์ ๋ํ ์ง์ ๋ฐฑ์คํ๋ฅผ ๊ตฌํํ๋ฉฐ, ์ด๋ ํ๋ก๋์ ์์ ์ฑ์ ํ์์ ์ ๋๋ค. ์ด๋ฉ์ผ ๊ฒ์ฆ ์๋น์ค๊ฐ ์๋ ์ ํ ์ค๋ฅ๋ฅผ ๋ฐํํ๊ฑฐ๋ ์ผ์์ ์ธ ๋ฌธ์ ๋ฅผ ๊ฒฝํํ ๋ ํด๋์ค๋ ์๋ ์ฌ์ด์ ์ ์ ๋ ๊ธด ์ง์ฐ ์๊ฐ์ ๋๊ณ ์๋์ผ๋ก ์ฌ์๋ํฉ๋๋ค.
Express.js ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ํตํฉ
๊ฒ์ฆ ๋ฏธ๋ค์จ์ด ์์ฑ
๋๋ถ๋ถ์ Node.js ์น ์ ํ๋ฆฌ์ผ์ด์ ์ Express.js ๋๋ ์ ์ฌํ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค. Express ๋ผ์ฐํธ์ ์ด๋ฉ์ผ ๊ฒ์ฆ์ ํตํฉํ๋ฉด ์์ ์ ์ถ ์ค์ ์ค์๊ฐ ์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ๊ฐ๋ฅํฉ๋๋ค. ์ฌ์ฉ์๋ ์ ํจํ์ง ์์ ์ด๋ฉ์ผ ์ฃผ์์ ๋ํ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ๋ฐ์ ๋ฑ๋ก ๊ฒฝํ์ด ๊ฐ์ ๋๋ ๋์์ ์ด๋ฉ์ผ ๋ชฉ๋ก ํ์ง์ด ๋ณดํธ๋ฉ๋๋ค.
๋ผ์ฐํธ ํธ๋ค๋ฌ์ ๋๋ฌํ๊ธฐ ์ ์ ์ด๋ฉ์ผ ์ฃผ์์ ์ ํจ์ฑ์ ๊ฒ์ฌํ๋ ๋ฏธ๋ค์จ์ด ํจ์๋ฅผ ๋ง๋์ธ์. ์ด ์ ๊ทผ ๋ฐฉ์์ ๊ฒ์ฆ ๋ก์ง์ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ๋ถ๋ฆฌํ์ฌ ์ฝ๋๋ฅผ ๋ ์ ์ง ๊ด๋ฆฌํ๊ธฐ ์ฝ๊ณ ํ ์คํธ ๊ฐ๋ฅํ๊ฒ ๋ง๋ญ๋๋ค.
// server.js
import express from 'express';
import EmailValidator from './EmailValidator.js';
const app = express();
const validator = new EmailValidator();
app.use(express.json());
// Middleware for email verification
const verifyEmailMiddleware = async (req, res, next) => {
const { email } = req.body;
if (!email) {
return res.status(400).json({ error: 'Email is required' });
}
try {
const result = await validator.verify(email);
if (!result.is_valid) {
return res.status(400).json({
error: 'Invalid email address',
details: result
});
}
if (result.is_disposable) {
return res.status(400).json({
error: 'Disposable email addresses are not allowed'
});
}
// Attach verification result for downstream use
req.emailVerification = result;
next();
} catch (error) {
console.error('Email verification failed:', error);
// Allow request to proceed but flag as unverified
req.emailVerification = { verified: false, error: error.message };
next();
}
};
// Registration endpoint with email verification
app.post('/api/register', verifyEmailMiddleware, async (req, res) => {
const { email, name, password } = req.body;
// Email is already verified by middleware
// Proceed with registration logic
res.json({
success: true,
message: 'Registration successful',
emailVerification: req.emailVerification
});
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
๊ฒ์ฆ ๊ฒฐ๊ณผ ์ฒ๋ฆฌ
๋ฏธ๋ค์จ์ด ์ ๊ทผ ๋ฐฉ์์ ์ด๋ฉ์ผ ๊ฒ์ฆ์ ์ผ๋ง๋ ์๊ฒฉํ๊ฒ ์ํํ ์ง์ ๋ํ ์ ์ฐ์ฑ์ ์ ๊ณตํฉ๋๋ค. ์ผ๋ถ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฒ์ฆ๋์ง ์์ ๋ชจ๋ ์ด๋ฉ์ผ์ ๊ฑฐ๋ถํ ์ ์์ง๋ง, ๋ค๋ฅธ ์ ํ๋ฆฌ์ผ์ด์ ์ ์๋ ๊ฒํ ๋ฅผ ์ํ ๊ฒฝ๊ณ ํ๋๊ทธ์ ํจ๊ป ์ด๋ฉ์ผ์ ์๋ฝํ ์ ์์ต๋๋ค. ์์ฒญ ๊ฐ์ฒด์ ์ฒจ๋ถ๋ ์ด๋ฉ์ผ ์ ํจ์ฑ ๊ฒ์ฌ ๊ฒฐ๊ณผ๋ฅผ ํตํด ๋ค์ด์คํธ๋ฆผ ํธ๋ค๋ฌ๊ฐ ์ธ๋ฐํ ๊ฒฐ์ ์ ๋ด๋ฆด ์ ์์ต๋๋ค.
๋ชฉ๋ก ์ ๋ฆฌ๋ฅผ ์ํ ์ผ๊ด ์ด๋ฉ์ผ ๊ฒ์ฆ
๋ฐฐ์น ์์ ์ ์ถ
์ค์๊ฐ ๊ฒ์ฆ์ด ๊ฐ๋ณ ์ฃผ์๋ฅผ ์ฒ๋ฆฌํ๋ ๋์, ๋ง์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๋์ ์ด๋ฉ์ผ ๋ชฉ๋ก์ ๊ฒ์ฆํด์ผ ํฉ๋๋ค. ๋ง์ผํ ํ์ ์ ๊ธฐ์ ์ผ๋ก ๊ตฌ๋ ์ ๋ชฉ๋ก์ ์ ๋ฆฌํ๊ณ , CRM ์์คํ ์ ์ ๊ธฐ์ ์ผ๋ก ์ ์ฅ๋ ์ฐ๋ฝ์ฒ์ ์ ํจ์ฑ์ ๊ฒ์ฌํฉ๋๋ค. ์ผ๊ด ๊ฒ์ฆ ์๋ํฌ์ธํธ๋ ์ฌ๋ฌ ์ด๋ฉ์ผ์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ์ฌ API ํธ์ถ์ ์ค์ด๊ณ ์ฒ๋ฆฌ๋์ ๊ฐ์ ํฉ๋๋ค.
์ผ๊ด ์์ ์ ๋จ์ผ ๊ฒ์ฆ๊ณผ ๋ค๋ฅธ ์ฒ๋ฆฌ๊ฐ ํ์ํฉ๋๋ค. ์์ ์ ์ถ, ์ํ ํด๋ง ๋ฐ ๊ฒฐ๊ณผ ๊ฒ์์ ๋ณ๋์ ์์ ์ผ๋ก ๊ด๋ฆฌํด์ผ ํฉ๋๋ค. ์ด ๋น๋๊ธฐ ํจํด์ ํตํด ์ด๋ฉ์ผ ๊ฒ์ฆ ์๋น์ค๋ ํ์์์ ์์ด ๋๋ ๋ชฉ๋ก์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
// batch-verify.js
import EmailValidator from './EmailValidator.js';
class BatchEmailValidator extends EmailValidator {
async submitBatch(emails) {
const response = await fetch(`${this.baseUrl}/verify/batch`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ emails })
});
if (!response.ok) {
throw new Error(`Batch submission failed: ${response.status}`);
}
return response.json();
}
async getBatchStatus(jobId) {
const response = await fetch(`${this.baseUrl}/verify/batch/${jobId}`, {
headers: {
'Authorization': `Bearer ${this.apiKey}`
}
});
if (!response.ok) {
throw new Error(`Status check failed: ${response.status}`);
}
return response.json();
}
async verifyBatch(emails, options = {}) {
const {
pollInterval = 5000,
maxWaitTime = 300000,
onProgress = () => {}
} = options;
// Submit the batch job
const { job_id } = await this.submitBatch(emails);
const startTime = Date.now();
// Poll for completion
while (Date.now() - startTime < maxWaitTime) {
const status = await this.getBatchStatus(job_id);
onProgress({
processed: status.processed,
total: status.total,
percentage: Math.round((status.processed / status.total) * 100)
});
if (status.status === 'completed') {
return status.results;
}
if (status.status === 'failed') {
throw new Error(`Batch job failed: ${status.error}`);
}
await this.sleep(pollInterval);
}
throw new Error('Batch verification timed out');
}
}
// Usage example
const batchValidator = new BatchEmailValidator();
const emails = [
'user1@example.com',
'user2@company.org',
'invalid@fake.domain',
// ... more emails
];
const results = await batchValidator.verifyBatch(emails, {
onProgress: (progress) => {
console.log(`Progress: ${progress.percentage}%`);
}
});
// Process results
const validEmails = results.filter(r => r.is_valid);
const invalidEmails = results.filter(r => !r.is_valid);
console.log(`Valid: ${validEmails.length}, Invalid: ${invalidEmails.length}`);
๊ฒฐ๊ณผ ํด๋ง
์ผ๊ด ๊ฒ์ฆ ๊ตฌํ์๋ ์งํ ์ํฉ ์ฝ๋ฐฑ์ด ํฌํจ๋์ด ์์ด ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ฌ์ฉ์์๊ฒ ๊ฒ์ฆ ์งํ ์ํฉ์ ํ์ํ๊ฑฐ๋ ๋ชจ๋ํฐ๋ง์ ์ํด ๊ธฐ๋กํ ์ ์์ต๋๋ค. ์ด๋ ์๋ฃํ๋ ๋ฐ ๋ช ๋ถ์ด ๊ฑธ๋ฆด ์ ์๋ ์์ฒ ๊ฐ์ ์ด๋ฉ์ผ ์ฃผ์๊ฐ ํฌํจ๋ ๋ชฉ๋ก์ ์ฒ๋ฆฌํ ๋ ํนํ ์ ์ฉํฉ๋๋ค.
์ค๋ฅ ์ฒ๋ฆฌ ๋ฐ ๋ณต์๋ ฅ
์ฌ์ฉ์ ์ ์ ์ค๋ฅ ํด๋์ค
ํ๋ก๋์ ์ด๋ฉ์ผ ๊ฒ์ฆ ํตํฉ์ ์ค๋ฅ๋ฅผ ์ฐ์ํ๊ฒ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค. ๋คํธ์ํฌ ๋ฌธ์ , API ์๋ ์ ํ ๋ฐ ์๋น์ค ๋ถ๊ฐ์ฉ์ ๋ถ์ฐ ์์คํ ์์ ๋ถ๊ฐํผํฉ๋๋ค. ์ ์ ํ ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ๊ตฌํํ๋ฉด ๊ฒ์ฆ ์๋น์ค์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋๋ผ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ด ๊ธฐ๋ฅ์ ์ ์งํ ์ ์์ต๋๋ค.
๋ค์ํ ์ค๋ฅ ์ ํ์ ๊ตฌ๋ถํ๋ ํฌ๊ด์ ์ธ ์ค๋ฅ ์ฒ๋ฆฌ ์ ๋ต์ ๋ง๋์ธ์. ์๋ ์ ํ๊ณผ ๊ฐ์ ์ผ์์ ์ค๋ฅ๋ ์ฌ์๋๊ฐ ํ์ํ์ง๋ง, ์๋ชป๋ API ํค์ ๊ฐ์ ์๊ตฌ์ ์ธ ์ค๋ฅ๋ ์ฆ๊ฐ์ ์ธ ์ฃผ์์ ์๋ฆผ์ด ํ์ํฉ๋๋ค.
// errors.js
class EmailVerificationError extends Error {
constructor(message, code, retryable = false) {
super(message);
this.name = 'EmailVerificationError';
this.code = code;
this.retryable = retryable;
}
}
class RateLimitError extends EmailVerificationError {
constructor(retryAfter) {
super('Rate limit exceeded', 'RATE_LIMITED', true);
this.retryAfter = retryAfter;
}
}
class AuthenticationError extends EmailVerificationError {
constructor() {
super('Invalid API key', 'AUTH_FAILED', false);
}
}
// Enhanced validator with error handling
class RobustEmailValidator extends EmailValidator {
async verify(email) {
try {
const response = await fetch(`${this.baseUrl}/verify`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ email })
});
if (response.status === 401) {
throw new AuthenticationError();
}
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || 60;
throw new RateLimitError(parseInt(retryAfter));
}
if (response.status >= 500) {
throw new EmailVerificationError(
'Service temporarily unavailable',
'SERVICE_ERROR',
true
);
}
if (!response.ok) {
const error = await response.json();
throw new EmailVerificationError(
error.message || 'Verification failed',
'API_ERROR',
false
);
}
return response.json();
} catch (error) {
if (error instanceof EmailVerificationError) {
throw error;
}
// Network or parsing error
throw new EmailVerificationError(
error.message,
'NETWORK_ERROR',
true
);
}
}
}
export { EmailVerificationError, RateLimitError, AuthenticationError, RobustEmailValidator };
๊ทธ๋ ์ด์คํ ๋๊ทธ๋ ์ด๋ฐ์ด์ ๊ตฌํ
์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋๋ ๋ค์ํ ์ค๋ฅ ์ ํ์ ์ ์ ํ๊ฒ ์ฒ๋ฆฌํ์ฌ ์ฌ์ฉ์์๊ฒ ์๋ฏธ ์๋ ํผ๋๋ฐฑ์ ์ ๊ณตํ๊ณ ์ด์ ํ์ ์ ์ ํ ์๋ฆผ์ ํธ๋ฆฌ๊ฑฐํ ์ ์์ต๋๋ค.
์ฑ๋ฅ์ ์ํ ์บ์ฑ ๊ตฌํ
์ธ๋ฉ๋ชจ๋ฆฌ ์บ์ ์ ๋ต
์ด๋ฉ์ผ ๊ฒ์ฆ API ํธ์ถ์ ๋น์ฉ๊ณผ ์ง์ฐ ์๊ฐ ์ธก๋ฉด์์ ๋ชจ๋ ๋น์ฉ์ด ๋ญ๋๋ค. ์บ์ฑ ๊ณ์ธต์ ๊ตฌํํ๋ฉด ๋์ผํ ์ด๋ฉ์ผ ์ฃผ์์ ๋ํ ์ค๋ณต ๊ฒ์ฆ์ ์ค์ด๋ฉด์ ์๋ต ์๊ฐ์ ๊ฐ์ ํ ์ ์์ต๋๋ค. ์ ์ค๊ณ๋ ์บ์๋ ์ด๋ฉ์ผ ์ ํจ์ฑ์ ๋์ ํน์ฑ์ ์กด์คํ๋ฉด์ ์๋ฏธ ์๋ ์ฑ๋ฅ ์ด์ ์ ์ ๊ณตํฉ๋๋ค.
์ฌ์ฉ ์ฌ๋ก์ ๋ฐ๋ผ ์ ์ ํ ์บ์ ๊ธฐ๊ฐ์ ์ ํํ์ธ์. ์ด๋ฉ์ผ ์ ํจ์ฑ์ ๋ณ๊ฒฝ๋ ์ ์์ต๋๋ค. ์ฌ์ํจ์ด ์ญ์ ๋๊ณ , ๋๋ฉ์ธ์ด ๋ง๋ฃ๋๊ณ , ์บ์น์ฌ ๊ตฌ์ฑ์ด ๋ณ๊ฒฝ๋ฉ๋๋ค. ๋๋ถ๋ถ์ ์ ํ๋ฆฌ์ผ์ด์ ์์ 24์๊ฐ์ ์บ์ ๊ธฐ๊ฐ์ ์ฑ๋ฅ๊ณผ ์ ํ์ฑ์ ๊ท ํ์ ๋ง์ถฅ๋๋ค.
// cached-validator.js
class CachedEmailValidator extends EmailValidator {
constructor(apiKey, cacheOptions = {}) {
super(apiKey);
this.cache = new Map();
this.cacheTTL = cacheOptions.ttl || 24 * 60 * 60 * 1000; // 24 hours
this.maxCacheSize = cacheOptions.maxSize || 10000;
}
getCacheKey(email) {
return email.toLowerCase().trim();
}
getCached(email) {
const key = this.getCacheKey(email);
const cached = this.cache.get(key);
if (!cached) return null;
if (Date.now() > cached.expiresAt) {
this.cache.delete(key);
return null;
}
return cached.result;
}
setCache(email, result) {
// Implement LRU eviction if cache is full
if (this.cache.size >= this.maxCacheSize) {
const oldestKey = this.cache.keys().next().value;
this.cache.delete(oldestKey);
}
const key = this.getCacheKey(email);
this.cache.set(key, {
result,
expiresAt: Date.now() + this.cacheTTL
});
}
async verify(email) {
// Check cache first
const cached = this.getCached(email);
if (cached) {
return { ...cached, fromCache: true };
}
// Perform verification
const result = await super.verify(email);
// Cache successful results
if (result && !result.error) {
this.setCache(email, result);
}
return { ...result, fromCache: false };
}
clearCache() {
this.cache.clear();
}
getCacheStats() {
return {
size: this.cache.size,
maxSize: this.maxCacheSize
};
}
}
export default CachedEmailValidator;
์บ์ ๋ฌดํจํ
๋๋ ์ฒ๋ฆฌ๋ฅผ ํ๋ ํ๋ก๋์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฒฝ์ฐ ๋ฉ๋ชจ๋ฆฌ ๋ด ์บ์ ๋์ Redis ๋๋ Memcached ์ฌ์ฉ์ ๊ณ ๋ คํ์ธ์. ์ด๋ฌํ ์ธ๋ถ ์บ์ ์ ์ฅ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ฌ์์ ์์๋ ์ง์๋๋ฉฐ ํด๋ฌ์คํฐ๋ง๋ ๋ฐฐํฌ์ ์ฌ๋ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ธ์คํด์ค ๊ฐ์ ๊ณต์ ํ ์ ์์ต๋๋ค.
์ด๋ฉ์ผ ๊ฒ์ฆ ํตํฉ ํ ์คํธ
Mock์ ์ฌ์ฉํ ์ ๋ ํ ์คํธ
ํฌ๊ด์ ์ธ ํ ์คํธ๋ฅผ ํตํด ์ด๋ฉ์ผ ๊ฒ์ฆ ํตํฉ์ด ๋ชจ๋ ์๋๋ฆฌ์ค์์ ์ฌ๋ฐ๋ฅด๊ฒ ์๋ํ๋์ง ํ์ธํ ์ ์์ต๋๋ค. ๋จ์ ํ ์คํธ๋ ๊ฐ๋ณ ๊ตฌ์ฑ ์์๋ฅผ ํ์ธํ๊ณ ํตํฉ ํ ์คํธ๋ ์ ์ ํ API ํต์ ์ ํ์ธํฉ๋๋ค. ์ค์ API ํธ์ถ์ ํผํ๊ธฐ ์ํด ๋จ์ ํ ์คํธ ์ค์ HTTP ๊ณ์ธต์ ๋ชจํนํ์ธ์.
// validator.test.js
import { jest } from '@jest/globals';
import EmailValidator from './EmailValidator.js';
describe('EmailValidator', () => {
let validator;
beforeEach(() => {
validator = new EmailValidator('test-api-key');
global.fetch = jest.fn();
});
test('returns valid result for valid email', async () => {
fetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({
is_valid: true,
is_deliverable: true,
is_disposable: false,
risk_score: 10
})
});
const result = await validator.verify('valid@example.com');
expect(result.is_valid).toBe(true);
expect(result.is_deliverable).toBe(true);
});
test('handles rate limiting with retry', async () => {
fetch
.mockResolvedValueOnce({ ok: false, status: 429 })
.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ is_valid: true })
});
const result = await validator.verify('test@example.com');
expect(fetch).toHaveBeenCalledTimes(2);
expect(result.is_valid).toBe(true);
});
test('throws after max retries exceeded', async () => {
fetch.mockResolvedValue({ ok: false, status: 500 });
await expect(validator.verify('test@example.com'))
.rejects.toThrow('API error: 500');
});
});
์ฃ์ง ์ผ์ด์ค ํ ์คํธ
๋คํธ์ํฌ ์ฅ์ , ์๋ชป๋ ํ์์ ์๋ต ๋ฐ ๋น์ ์์ ์ธ ์ด๋ฉ์ผ ํ์๊ณผ ๊ฐ์ ์ฃ์ง ์ผ์ด์ค์ ๋ํ ํ ์คํธ๋ฅผ ํฌํจํ์ธ์. ์ด๋ฉ์ผ ์ ํจ์ฑ ๊ฒ์ฌ๊ธฐ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ค๋จ์ํค์ง ์๊ณ ๋ชจ๋ ์๋๋ฆฌ์ค๋ฅผ ์ฐ์ํ๊ฒ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค.
๋ชจ๋ํฐ๋ง ๋ฐ ๋ก๊น ๋ชจ๋ฒ ์ฌ๋ก
๊ตฌ์กฐํ๋ ๋ก๊น
ํ๋ก๋์ ์ด๋ฉ์ผ ๊ฒ์ฆ ํตํฉ์ ์ฑ๋ฅ ์ถ์ , ๋ฌธ์ ์๋ณ ๋ฐ ๋น์ฉ ์ต์ ํ๋ฅผ ์ํ ๋ชจ๋ํฐ๋ง์ด ํ์ํฉ๋๋ค. ๊ฒ์ฆ ๊ฒฐ๊ณผ, ์๋ต ์๊ฐ ๋ฐ ์ค๋ฅ์จ์ ์บก์ฒํ๋ ๊ตฌ์กฐํ๋ ๋ก๊น ์ ๊ตฌํํ์ธ์.
// monitored-validator.js
class MonitoredEmailValidator extends EmailValidator {
constructor(apiKey, logger = console) {
super(apiKey);
this.logger = logger;
this.metrics = {
totalRequests: 0,
successfulVerifications: 0,
failedVerifications: 0,
cacheHits: 0,
totalLatency: 0
};
}
async verify(email) {
const startTime = Date.now();
this.metrics.totalRequests++;
try {
const result = await super.verify(email);
const latency = Date.now() - startTime;
this.metrics.successfulVerifications++;
this.metrics.totalLatency += latency;
this.logger.info({
event: 'email_verification',
email: this.maskEmail(email),
is_valid: result.is_valid,
latency_ms: latency
});
return result;
} catch (error) {
this.metrics.failedVerifications++;
this.logger.error({
event: 'email_verification_error',
email: this.maskEmail(email),
error: error.message,
latency_ms: Date.now() - startTime
});
throw error;
}
}
maskEmail(email) {
const [local, domain] = email.split('@');
const maskedLocal = local.charAt(0) + '***' + local.slice(-1);
return `${maskedLocal}@${domain}`;
}
getMetrics() {
return {
...this.metrics,
averageLatency: this.metrics.totalRequests > 0
? Math.round(this.metrics.totalLatency / this.metrics.totalRequests)
: 0,
successRate: this.metrics.totalRequests > 0
? (this.metrics.successfulVerifications / this.metrics.totalRequests * 100).toFixed(2)
: 0
};
}
}
export default MonitoredEmailValidator;
๋ฉํธ๋ฆญ ์ถ์
API ๋ฌธ์ ๋๋ ๋จ์ฉ ์๋๋ฅผ ๋ํ๋ผ ์ ์๋ ๋์ ์ค๋ฅ์จ ๋๋ ๋น์ ์์ ์ธ ํจํด์ ๋ํ ์๋ฆผ์ ์ค์ ํ์ธ์. ๋ชจ๋ํฐ๋ง ๋์๋ณด๋๋ ๊ฒ์ฆ ํจํด์ ์ดํดํ๊ณ ์๊ฐ์ด ์ง๋จ์ ๋ฐ๋ผ ๊ตฌํ์ ์ต์ ํํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
๋ณด์ ๊ณ ๋ ค ์ฌํญ
API ์๊ฒฉ ์ฆ๋ช ๋ณดํธ
์ด๋ฉ์ผ ๊ฒ์ฆ ํตํฉ์ ์ ์ฌ์ ์ผ๋ก ๋ฏผ๊ฐํ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ฉฐ ์ ์คํ ๋ณด์ ๊ณ ๋ ค๊ฐ ํ์ํฉ๋๋ค. API ํค๋ฅผ ๋ณดํธํ๊ณ , ์ ๋ ฅ์ ์ ํจ์ฑ์ ๊ฒ์ฌํ๊ณ , ์์ฒด ์๋ํฌ์ธํธ์ ์๋ ์ ํ์ ๊ตฌํํ์ฌ ๋จ์ฉ์ ๋ฐฉ์งํ์ธ์.
BillionVerify API ํค๋ฅผ ํด๋ผ์ด์ธํธ ์ธก ์ฝ๋์ ๋ ธ์ถํ์ง ๋ง์ธ์. ๋ชจ๋ ๊ฒ์ฆ ์์ฒญ์ API ์๊ฒฉ ์ฆ๋ช ์ ์์ ํ๊ฒ ๋ณด์ ํ ๋ฐฑ์๋ ์๋ฒ๋ฅผ ํตํด ๋ผ์ฐํ ๋์ด์ผ ํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ ์์ ์ธ ํ์์๊ฐ ์์ ์ ๋ชฉ์ ์ ์ํด API ํ ๋น๋์ ์ฌ์ฉํ๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
์ ๋ ฅ ๊ฒ์ฆ ๋ฐ ์๋ ์ ํ
๊ฒ์ฆ API์ ์ด๋ฉ์ผ์ ๋ณด๋ด๊ธฐ ์ ์ ์ ๋ ฅ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ๊ตฌํํ์ธ์. ์ธก์์ ๊ธฐ๋ณธ ํ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํํ๋ฉด ๋ถํ์ํ API ํธ์ถ์ ์ค์ด๊ณ ๋ช ๋ฐฑํ ์ ํจํ์ง ์์ ์ ๋ ฅ์ ๋ํด ๋ ๋น ๋ฅธ ํผ๋๋ฐฑ์ ์ ๊ณตํฉ๋๋ค.
// secure-validator.js
class SecureEmailValidator extends EmailValidator {
constructor(apiKey, options = {}) {
super(apiKey);
this.rateLimiter = new Map();
this.maxRequestsPerMinute = options.maxRequestsPerMinute || 100;
}
validateEmailFormat(email) {
if (!email || typeof email !== 'string') {
throw new Error('Email must be a non-empty string');
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
throw new Error('Invalid email format');
}
if (email.length > 254) {
throw new Error('Email exceeds maximum length');
}
return email.toLowerCase().trim();
}
checkRateLimit(clientId) {
const now = Date.now();
const windowStart = now - 60000;
if (!this.rateLimiter.has(clientId)) {
this.rateLimiter.set(clientId, []);
}
const requests = this.rateLimiter.get(clientId);
const recentRequests = requests.filter(time => time > windowStart);
if (recentRequests.length >= this.maxRequestsPerMinute) {
throw new Error('Rate limit exceeded. Please try again later.');
}
recentRequests.push(now);
this.rateLimiter.set(clientId, recentRequests);
}
async verify(email, clientId = 'default') {
this.checkRateLimit(clientId);
const sanitizedEmail = this.validateEmailFormat(email);
return super.verify(sanitizedEmail);
}
}
export default SecureEmailValidator;
๊ฒฐ๋ก
Node.js ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ด๋ฉ์ผ ๊ฒ์ฆ์ ๊ตฌํํ๋ฉด ๊ณ ํ์ง ์ด๋ฉ์ผ ๋ชฉ๋ก์ ์ ์งํ๊ณ ๋ฐ์ ์ ํํ์ ๋ณดํธํ๋ ๊ธฐ๋ฐ์ด ๋ง๋ จ๋ฉ๋๋ค. ์ด ํํ ๋ฆฌ์ผ์์ ๋ค๋ฃฌ ๊ธฐ์ ์ธ ๊ธฐ๋ณธ API ํตํฉ๋ถํฐ ์บ์ฑ, ์ค๋ฅ ์ฒ๋ฆฌ ๋ฐ ๋ชจ๋ํฐ๋ง์ ํฌํจํ ํ๋ก๋์ ์์ค ํจํด๊น์ง, ๋ชจ๋ Node.js ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ ฅํ ์ด๋ฉ์ผ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ๊ตฌ์ถํ ์ ์๋ ์ญ๋์ ์ ๊ณตํฉ๋๋ค.
BillionVerify์ Node.js ์ด๋ฉ์ผ ๊ฒ์ฆ API๋ ์ค์๊ฐ ๋จ์ผ ์ด๋ฉ์ผ ์ ํจ์ฑ ๊ฒ์ฌ ๋ฐ ์ผ๊ด ์ฒ๋ฆฌ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ฌ Node.js์ ์ํํ๊ฒ ํตํฉ๋ฉ๋๋ค. ์๋ต ๋ฐ์ดํฐ๋ฅผ ํตํด ๊ฐ๋จํ ์ ํจ/๋ฌดํจ ํ๋จ๋ถํฐ ์ ๊ตํ ์ํ ๊ธฐ๋ฐ ํํฐ๋ง๊น์ง ์ด๋ฉ์ผ ์๋ฝ์ ๋ํ ์ธ๋ฐํ ์์ฌ ๊ฒฐ์ ์ด ๊ฐ๋ฅํฉ๋๋ค.
API ํจํด์ ์ดํดํ๊ธฐ ์ํด ๊ธฐ๋ณธ ๊ตฌํ๋ถํฐ ์์ํ ๋ค์ ์ ํ๋ฆฌ์ผ์ด์ ์๊ตฌ ์ฌํญ์ด ๋ฐ์ ํจ์ ๋ฐ๋ผ ์ ์ง์ ์ผ๋ก ์บ์ฑ, ๋ชจ๋ํฐ๋ง ๋ฐ ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ์ถ๊ฐํ์ธ์. ์ฌ๊ธฐ์ ์ค๋ช ๋ ์ด๋ฉ์ผ ์ ํจ์ฑ ๊ฒ์ฌ๊ธฐ ํจํด์ ์คํํธ์ MVP์์ ์๋ฐฑ๋ง ๊ฑด์ ๊ฒ์ฆ์ ์ฒ๋ฆฌํ๋ ์ํฐํ๋ผ์ด์ฆ๊ธ ์ ํ๋ฆฌ์ผ์ด์ ๊น์ง ํ์ฅ๋ฉ๋๋ค.
์ฌ์ฉ์ ๋ฑ๋ก ์์คํ ์ ๊ตฌ์ถํ๋ , ๋ง์ผํ ๋ชฉ๋ก์ ์ ๋ฆฌํ๋ , ์ฐ๋ฝ์ฒ ์์ ์ ์ถ์ ์ ํจ์ฑ์ ๊ฒ์ฌํ๋ , ์ ์ ํ ์ด๋ฉ์ผ ์ฃผ์ ํ์ธ์ ์ด๋ฉ์ผ ์ ๋ฌ ๊ฐ๋ฅ์ฑ์ ๋ณดํธํ๊ณ ๋ฉ์์ง๊ฐ ์ค์ ์์ ์์๊ฒ ๋๋ฌํ๋๋ก ๋ณด์ฅํฉ๋๋ค. BillionVerify ๊ณ์ ์ ๊ฐ์ ํ๊ณ ์ค๋ Node.js ์ ํ๋ฆฌ์ผ์ด์ ์ ์ด๋ฉ์ผ ๊ฒ์ฆ์ ํตํฉํ์ฌ ์ฒซ๊ฑธ์์ ๋ด๋๋์ธ์.