Integrate Email Insights into Your Node.js Application: A Developer's Guide
This guide shows how to integrate Email Insights into a Node.js application using the official SDK. You will learn how to run single validations, launch batch jobs with polling, handle errors gracefully, and build production-ready integrations with proper configuration and security.
If you are new to the product, start with the overview at /products/email-insights and the bulk features at /email-insights/bulk-email-validation. For security and compliance details, see /security and /legal/gdpr-notice.
SDK Resources:
- Node.js SDK on GitHub - Source code, issues, and contributions
- Complete SDK Documentation - Installation guides, API reference, and examples
Requirements
- Node.js 16.x or later
- npm or yarn
- A valid API key (see below for instructions)
Get Your API Key
- Log into your Opportify dashboard
- Navigate to Access Control → API Keys
- Generate a new API key or copy your existing key
- Store it securely (environment variable, secrets manager—never commit to source control)
Install the SDK
npm install @opportify/sdk-nodejs
Quick start: single email analysis
import { EmailInsights } from '@opportify/sdk-nodejs';
const emailInsights = new EmailInsights({
version: '1.0',
apiKey: process.env.OPPORTIFY_API_KEY // Load securely per your policy
});
async function analyzeEmail() {
try {
const result = await emailInsights.analyze({
email: "user@example.com",
enableAutoCorrection: true,
enableAI: true
});
console.log({
isDeliverable: result.isDeliverable, // "yes" | "no" | "unknown"
isCatchAll: result.isCatchAll,
isMailboxFull: result.isMailboxFull,
isReachable: result.isReachable,
normalizedScore: result.normalizedScore,
riskLevel: result.riskLevel || result.riskReport?.level,
provider: result.provider,
domain: result.domain
});
} catch (error) {
console.error('Email analysis failed:', error.message);
throw error;
}
}
analyzeEmail();
When to enable AI and autocorrection
enableAutoCorrection: truehelps correct common user typos in form inputs (e.g., "gmial.com" → "gmail.com").enableAI: trueadds domain enrichment and risk signals that improve decisions for gating, segmentation, and fraud protection.
Batch analysis with async/await
Use batch jobs for large lists. A job returns a jobId that you can poll until results are ready.
import { EmailInsights } from '@opportify/sdk-nodejs';
const emailInsights = new EmailInsights({
version: '1.0',
apiKey: process.env.OPPORTIFY_API_KEY
});
async function processBatch() {
const params = {
emails: [
"user1@company.com",
"user2@domain.org",
"test@example.com"
],
name: "User Signup Validation",
enableAutoCorrection: true,
enableAI: true
};
try {
// Start batch job
const batch = await emailInsights.batchAnalyze(params);
console.log('Batch job started:', batch.jobId);
// Poll for completion
const result = await waitForCompletion(batch.jobId);
console.log('Download URLs:');
console.log('CSV:', result.downloadUrls?.csv);
console.log('JSON:', result.downloadUrls?.json);
} catch (error) {
console.error('Batch processing failed:', error.message);
throw error;
}
}
async function waitForCompletion(jobId, maxWaitMs = 600000) {
const startTime = Date.now();
let delay = 2000; // Start with 2 second delay
while (true) {
const status = await emailInsights.getBatchStatus(jobId);
const state = status.status;
const progress = status.progress || 0;
console.log(`Status: ${state} - Progress: ${progress}%`);
if (state === 'COMPLETED') {
return status;
}
if (state === 'ERROR') {
const description = status.statusDescription || 'Unknown error';
throw new Error(`Batch job failed: ${description}`);
}
if (Date.now() - startTime > maxWaitMs) {
throw new Error('Batch job timeout - did not complete within time limit');
}
// Exponential backoff with cap at 30 seconds
await sleep(Math.min(delay, 30000));
delay = Math.min(delay * 1.6, 30000);
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
processBatch();
Batch with file upload
import { EmailInsights } from '@opportify/sdk-nodejs';
import fs from 'fs';
const emailInsights = new EmailInsights({
version: '1.0',
apiKey: process.env.OPPORTIFY_API_KEY
});
async function uploadAndProcess() {
try {
const batch = await emailInsights.batchAnalyzeFile('./emails.csv', {
name: 'Monthly Email List Cleanup',
enableAI: true,
enableAutoCorrection: true
});
console.log('File uploaded, job ID:', batch.jobId);
// Poll for results
const result = await waitForCompletion(batch.jobId);
console.log('Processing complete!');
} catch (error) {
console.error('File upload failed:', error.message);
throw error;
}
}
uploadAndProcess();
Practical tips for batches
- Split large uploads into smaller jobs (5,000-10,000 emails per job) for faster completion and easier retries.
- Persist
jobIdin your database to track results even if your application restarts. - Use exponential backoff when polling to reduce unnecessary API calls.
- Consider implementing webhook listeners for batch completion notifications (if available).
Handling errors safely
Always wrap API calls in try-catch blocks to handle exceptions cleanly.
import { EmailInsights } from '@opportify/sdk-nodejs';
const emailInsights = new EmailInsights({
version: '1.0',
apiKey: process.env.OPPORTIFY_API_KEY
});
async function safeAnalyze(email) {
try {
return await emailInsights.analyze({
email,
enableAI: true,
enableAutoCorrection: true
});
} catch (error) {
if (error.response) {
// API returned an error response
console.error('API error:', {
status: error.response.status,
message: error.response.data?.message || error.message
});
// Handle specific error codes
if (error.response.status === 403) {
throw new Error('API key invalid or feature not available on your plan');
}
if (error.response.status === 429) {
throw new Error('Rate limit exceeded - implement backoff and retry');
}
} else if (error.request) {
// Request was made but no response received
console.error('Network error - no response from server');
throw new Error('Connection to validation service failed');
} else {
// Something else happened
console.error('Request setup error:', error.message);
}
throw error;
}
}
Interpreting results and taking action
Deliverability is tri-state
isDeliverable returns a string: yes, no, or unknown. Combine it with isMailboxFull, isCatchAll, and isReachable to make informed decisions.
Risk scoring model
Email Insights provides a normalizedScore on a 200 to 1000 scale with corresponding risk levels:
| Risk Level | Score Range | Guidance |
|---|---|---|
| Lowest | ≤ 300 | Safe to include in primary campaigns |
| Low | 300-400 | Generally safe; monitor engagement |
| Medium | 400-600 | Consider revalidation or secondary flows |
| High | 600-800 | Exclude from high-value campaigns; verify |
| Highest | > 800 | Reject; investigate for fraud or traps |
Decision helper (tri-state aware)
function classifyContact(result) {
const deliverable = (result.isDeliverable || '').toLowerCase();
const riskLevel = (result.riskLevel || result.riskReport?.level || '').toLowerCase();
const isCatchAll = Boolean(result.isCatchAll);
const isMailboxFull = Boolean(result.isMailboxFull);
const isReachable = Boolean(result.isReachable);
const signals = result.signals || {};
const isDisposable = Boolean(signals.isDisposableDomain);
// Immediate rejection criteria
if (isDisposable || riskLevel === 'highest') {
return 'reject';
}
// Handle non-deliverable emails
if (deliverable === 'no') {
return isMailboxFull ? 'revalidate' : 'reject';
}
// Handle unknown deliverability
if (deliverable === 'unknown') {
if (riskLevel === 'high') return 'review';
if (isCatchAll || !isReachable) return 'revalidate';
return 'review';
}
// Handle deliverable emails (yes)
if (riskLevel === 'high') return 'review';
if (riskLevel === 'medium' || isCatchAll) return 'revalidate';
return 'accept';
}
// Usage example
const result = await emailInsights.analyze({ email: 'user@example.com' });
const action = classifyContact(result);
console.log(`Action for ${result.emailAddress}: ${action}`);
Production hardening
Configuration and secrets management
- Store your
OPPORTIFY_API_KEYsecurely using environment variables or your preferred secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.). - Never hard-code the API key or commit it to version control.
- Avoid logging the full API key or payloads containing personal data.
- Use
.envfiles locally with proper.gitignoreconfiguration.
Access Control Lists (ACLs) Email Insights supports API Key Access Control Lists that restrict usage by IP address or range. Configure these in your account dashboard to ensure only approved systems can access the API.
Caching for performance
Implement caching to reduce API calls and improve response times:
import { EmailInsights } from '@opportify/sdk-nodejs';
class CachedEmailValidator {
constructor(apiKey) {
this.client = new EmailInsights({
version: '1.0',
apiKey
});
this.cache = new Map();
this.cacheDuration = 3600000; // 1 hour in ms
this.maxCacheSize = 10000; // Limit cache size to prevent unbounded growth
}
async validate(email) {
// Check cache first
const cached = this.getFromCache(email);
if (cached) return cached;
// Fetch from API
const result = await this.client.analyze({
email,
enableAI: true,
enableAutoCorrection: true
});
// Save to cache
this.saveToCache(email, result);
return result;
}
getFromCache(email) {
const cached = this.cache.get(email);
if (!cached) return null;
const age = Date.now() - cached.timestamp;
if (age > this.cacheDuration) {
this.cache.delete(email);
return null;
}
return cached.result;
}
saveToCache(email, result) {
// Prevent unbounded cache growth - use LRU-like behavior
if (this.cache.size >= this.maxCacheSize) {
// Remove oldest entries (first 20% of cache)
const entriesToRemove = Math.floor(this.maxCacheSize * 0.2);
const iterator = this.cache.keys();
for (let i = 0; i < entriesToRemove; i++) {
const key = iterator.next().value;
this.cache.delete(key);
}
}
this.cache.set(email, {
result,
timestamp: Date.now()
});
}
isAcceptable(result, options = {}) {
const {
maxRiskScore = 600,
allowDisposable = false,
allowRole = true,
requireAuth = false
} = options;
if (result.isDeliverable !== 'yes') return false;
if (result.riskReport?.score > maxRiskScore) return false;
if (!allowDisposable && result.emailType === 'disposable') return false;
if (!allowRole && result.addressSignals?.isRoleAddress) return false;
if (requireAuth && (!result.emailDNS?.spfValid || !result.emailDNS?.dkimConfigured)) return false;
return true;
}
}
// Usage in Express application
import express from 'express';
const app = express();
// Configure body parsing middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const validator = new CachedEmailValidator(process.env.OPPORTIFY_API_KEY);
app.post('/api/signup', async (req, res) => {
const { email, name } = req.body;
try {
const validation = await validator.validate(email);
if (!validator.isAcceptable(validation, {
maxRiskScore: 600,
allowDisposable: false,
allowRole: false
})) {
return res.status(400).json({
error: 'Invalid email address',
details: {
riskScore: validation.riskReport?.score,
deliverable: validation.isDeliverable,
emailType: validation.emailType
}
});
}
// Proceed with registration
const user = await createUser({ email, name });
res.json({ success: true, userId: user.id });
} catch (error) {
console.error('Validation error:', error);
// Graceful degradation - don't block registration on validation failure
res.json({
success: true,
warning: 'Email validation temporarily unavailable'
});
}
});
Timeouts and retries
async function analyzeWithRetry(email, maxRetries = 3) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await emailInsights.analyze({
email,
enableAI: true,
enableAutoCorrection: true
});
} catch (error) {
lastError = error;
// Don't retry on client errors (4xx)
if (error.response?.status >= 400 && error.response?.status < 500) {
throw error;
}
// Exponential backoff for retries
if (attempt < maxRetries) {
const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
await sleep(delay);
}
}
}
throw lastError;
}
Observability and monitoring
import { EmailInsights } from '@opportify/sdk-nodejs';
class MonitoredEmailValidator {
constructor(apiKey, logger) {
this.client = new EmailInsights({
version: '1.0',
apiKey
});
this.logger = logger;
this.metrics = {
totalCalls: 0,
errors: 0,
cacheHits: 0
};
}
async validate(email) {
const startTime = Date.now();
this.metrics.totalCalls++;
try {
const result = await this.client.analyze({
email,
enableAI: true,
enableAutoCorrection: true
});
const duration = Date.now() - startTime;
this.logger.info('Email validation successful', {
email: email.replace(/(?<=.{2}).(?=.*@)/g, '*'), // Mask email
duration,
riskLevel: result.riskReport?.level,
isDeliverable: result.isDeliverable
});
return result;
} catch (error) {
this.metrics.errors++;
const duration = Date.now() - startTime;
this.logger.error('Email validation failed', {
email: email.replace(/(?<=.{2}).(?=.*@)/g, '*'),
duration,
error: error.message,
statusCode: error.response?.status
});
throw error;
}
}
getMetrics() {
return {
...this.metrics,
errorRate: this.metrics.totalCalls > 0
? (this.metrics.errors / this.metrics.totalCalls * 100).toFixed(2) + '%'
: '0%'
};
}
}
Local testing checklist
- Use synthetic emails covering edge cases: disposable domains, catch-all domains, corporate domains, common typos.
- Test routing logic for each
riskLevelandisDeliverablevalue combination. - Verify graceful handling of invalid API keys, network timeouts, and rate limits.
- Test batch processing with both small and large datasets.
- Validate that caching works correctly and respects TTL.
Where to go next
SDK & Documentation:
- Node.js SDK on GitHub - Source code, examples, and issue tracking
- Complete SDK Documentation - Detailed API reference and guides
- API Documentation - REST API endpoints and authentication
Product & Features:
- Email Insights Product Overview - Features and use cases
- Bulk Email Validation - Process large email lists
- Integrations - Zapier, HubSpot, Crisp, and more
Security & Compliance:
- Security Overview - Our security practices
- GDPR Notice - Data protection and privacy
Reference: SDK methods
new EmailInsights({ version, apiKey })emailInsights.analyze(params)emailInsights.batchAnalyze(params)emailInsights.batchAnalyzeFile(filePath, params)emailInsights.getBatchStatus(jobId)