Best Practices
Follow these best practices to maximize the effectiveness of your email validation integration.
Performance
Section titled “Performance”Cache Validation Results
Section titled “Cache Validation Results”Validation results are stable—cache them to avoid redundant API calls.
const cache = new Map<string, EmailValidationResult>();const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
async function validateWithCache(email: string) { const key = email.toLowerCase().trim();
const cached = cache.get(key); if (cached && Date.now() - cached.timestamp < CACHE_TTL) { return cached.result; }
const result = await client.validate(email); cache.set(key, { result, timestamp: Date.now() });
return result;}Use Quick Mode for Real-Time Validation
Section titled “Use Quick Mode for Real-Time Validation”For form validation where speed matters, use quick mode:
// Real-time form validation (< 100ms)const quick = await client.validate(email, { quickMode: true });
// Background verification (full checks)const full = await client.validate(email);Quick mode skips:
- SMTP verification
- Catch-all detection
- Some reputation checks
Deduplicate Before Batch
Section titled “Deduplicate Before Batch”Remove duplicates before batch validation to save quota:
function deduplicateEmails(emails: string[]): string[] { const seen = new Set<string>(); return emails.filter(email => { const normalized = email.toLowerCase().trim(); if (seen.has(normalized)) return false; seen.add(normalized); return true; });}
const unique = deduplicateEmails(rawEmails);const results = await client.validateBatch(unique);Process Large Lists in Batches
Section titled “Process Large Lists in Batches”async function processLargeList(emails: string[]) { const BATCH_SIZE = 100; const results = [];
for (let i = 0; i < emails.length; i += BATCH_SIZE) { const batch = emails.slice(i, i + BATCH_SIZE); const response = await client.validateBatch(batch); results.push(...response.results); }
return results;}Reliability
Section titled “Reliability”Handle Rate Limits Gracefully
Section titled “Handle Rate Limits Gracefully”import { RateLimitError } from '@spamidate/sdk';
async function validateWithRetry(email: string) { try { return await client.validate(email); } catch (error) { if (error instanceof RateLimitError) { await sleep(error.retryAfter * 1000); return client.validate(email); } throw error; }}Implement Exponential Backoff
Section titled “Implement Exponential Backoff”For transient errors, use exponential backoff:
async function validateWithBackoff(email: string, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await client.validate(email); } catch (error) { if (error.isRetryable && attempt < maxRetries - 1) { const delay = Math.pow(2, attempt) * 1000; await sleep(delay); continue; } throw error; } }}Monitor Quota Usage
Section titled “Monitor Quota Usage”Check quota before large operations:
async function validateListSafely(emails: string[]) { const usage = await client.getUsage();
if (emails.length > usage.quota.remaining) { throw new Error(`Insufficient quota: need ${emails.length}, have ${usage.quota.remaining}`); }
if (usage.quota.percentage > 80) { console.warn(`Warning: ${usage.quota.percentage}% quota used`); }
return client.validateBatch(emails);}Security
Section titled “Security”Never Expose API Keys
Section titled “Never Expose API Keys”// WRONG - API key in client-side codeconst client = new Spamidate({ apiKey: 'spm_live_...' });
// RIGHT - API key in server-side environment variableconst client = new Spamidate({ apiKey: process.env.SPAMIDATE_API_KEY });Use API Routes for Client-Side Validation
Section titled “Use API Routes for Client-Side Validation”Create a server-side endpoint:
// pages/api/validate-email.ts (Next.js example)export async function POST(req: Request) { const { email } = await req.json();
const client = new Spamidate({ apiKey: process.env.SPAMIDATE_API_KEY }); const result = await client.validate(email);
// Return only necessary fields return Response.json({ isValid: result.isValid, score: result.score, suggestion: result.checks.typoSuggestion?.metadata?.suggestion, });}Rotate API Keys Periodically
Section titled “Rotate API Keys Periodically”- Generate a new API key in the dashboard
- Update your environment variables
- Deploy the changes
- Disable the old key after confirming the new one works
Form Integration
Section titled “Form Integration”Real-Time Validation Pattern
Section titled “Real-Time Validation Pattern”// React example with debouncingfunction EmailInput({ onValidated }) { const [email, setEmail] = useState(''); const [status, setStatus] = useState<'idle' | 'validating' | 'valid' | 'invalid'>('idle');
const validateEmail = useMemo( () => debounce(async (value: string) => { if (!value) return; setStatus('validating');
const response = await fetch('/api/validate-email', { method: 'POST', body: JSON.stringify({ email: value }), }); const result = await response.json();
setStatus(result.isValid ? 'valid' : 'invalid'); onValidated(result); }, 500), [] );
return ( <input type="email" value={email} onChange={(e) => { setEmail(e.target.value); validateEmail(e.target.value); }} /> );}Show Typo Suggestions
Section titled “Show Typo Suggestions”if (result.checks.typoSuggestion?.metadata?.suggestion) { return ( <div> Did you mean{' '} <button onClick={() => setEmail(result.checks.typoSuggestion.metadata.suggestion)}> {result.checks.typoSuggestion.metadata.suggestion} </button> ? </div> );}Handle Different Severities
Section titled “Handle Different Severities”function getValidationMessage(result: EmailValidationResult) { switch (result.severity) { case 'valid': return { type: 'success', message: 'Email verified' }; case 'warning': return { type: 'warning', message: result.recommendations[0] }; case 'invalid': return { type: 'error', message: result.recommendations[0] || 'Invalid email' }; }}List Cleaning
Section titled “List Cleaning”Segment by Quality
Section titled “Segment by Quality”function segmentByQuality(results: EmailValidationResult[]) { return { excellent: results.filter(r => r.score >= 90), good: results.filter(r => r.score >= 70 && r.score < 90), risky: results.filter(r => r.score >= 40 && r.score < 70), invalid: results.filter(r => r.score < 40), };}Export Clean Lists
Section titled “Export Clean Lists”function exportCleanList(results: EmailValidationResult[], minScore = 70) { return results .filter(r => r.score >= minScore) .filter(r => r.checks.disposable?.passed !== false) .map(r => r.email);}Generate Reports
Section titled “Generate Reports”function generateReport(results: BatchValidationResponse) { const checks = { disposable: results.results.filter(r => r.checks.disposable?.passed === false).length, roleBased: results.results.filter(r => r.checks.roleBased?.passed === false).length, typos: results.results.filter(r => r.checks.typoSuggestion?.metadata?.hasTypo).length, };
return { total: results.total, valid: results.valid, invalid: results.invalid, warning: results.warning, validRate: ((results.valid / results.total) * 100).toFixed(1) + '%', issues: checks, };}Webhook Integration
Section titled “Webhook Integration”Verify Signatures
Section titled “Verify Signatures”Always verify webhook signatures:
function verifyWebhook(payload: string, signature: string, timestamp: string) { const expected = crypto .createHmac('sha256', process.env.WEBHOOK_SECRET) .update(`${timestamp}.${payload}`) .digest('hex');
return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) );}Handle Duplicates
Section titled “Handle Duplicates”Webhooks may be delivered multiple times:
const processedEvents = new Set<string>();
function handleWebhook(event: WebhookEvent) { if (processedEvents.has(event.id)) { return; // Already processed }
processEvents.add(event.id); // Process event...}Respond Quickly
Section titled “Respond Quickly”Acknowledge webhooks immediately, process asynchronously:
app.post('/webhooks', (req, res) => { res.status(200).send('OK'); processWebhookAsync(req.body);});Monitoring
Section titled “Monitoring”Track Validation Metrics
Section titled “Track Validation Metrics”async function validateAndTrack(email: string) { const start = Date.now(); const result = await client.validate(email); const duration = Date.now() - start;
// Log metrics metrics.track('email_validation', { duration, score: result.score, severity: result.severity, isDisposable: result.checks.disposable?.passed === false, });
return result;}Set Up Alerts
Section titled “Set Up Alerts”async function checkQuotaAndAlert() { const usage = await client.getUsage();
if (usage.quota.percentage >= 80) { await sendSlackAlert(`Spamidate quota at ${usage.quota.percentage}%`); }
if (usage.recentErrors.rateLimited > 10) { await sendSlackAlert('High rate limit errors detected'); }}
// Run hourlysetInterval(checkQuotaAndAlert, 60 * 60 * 1000);Anti-Patterns to Avoid
Section titled “Anti-Patterns to Avoid”Don’t Validate Every Keystroke
Section titled “Don’t Validate Every Keystroke”// WRONG - too many API callsinput.addEventListener('input', () => validate(input.value));
// RIGHT - debounceinput.addEventListener('input', debounce(() => validate(input.value), 500));Don’t Skip Error Handling
Section titled “Don’t Skip Error Handling”// WRONG - no error handlingconst result = await client.validate(email);
// RIGHT - handle errorstry { const result = await client.validate(email);} catch (error) { if (error instanceof RateLimitError) { // Handle rate limit } // Handle other errors}Don’t Hardcode Thresholds
Section titled “Don’t Hardcode Thresholds”// WRONG - magic numbersif (result.score > 65) { ... }
// RIGHT - configurable thresholdsconst SCORE_THRESHOLDS = { signup: 70, marketing: 80, transactional: 50,};
if (result.score >= SCORE_THRESHOLDS[context]) { ... }Don’t Ignore Warnings
Section titled “Don’t Ignore Warnings”// WRONG - only check isValidif (result.isValid) { proceed(); }
// RIGHT - consider severity and checksif (result.isValid && result.severity !== 'warning') { proceed();} else if (result.checks.typoSuggestion?.metadata?.hasTypo) { showSuggestion();}