Integrate Email Insights into Your Java Application: A Developer's Guide
This guide demonstrates how to integrate Email Insights into a Java application using the official SDK. You will learn how to perform single validations, launch batch jobs with polling, handle errors robustly, and build production-ready integrations with proper configuration, caching, 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:
- Java SDK on GitHub - Source code, issues, and contributions
- Complete SDK Documentation - Installation guides, API reference, and examples
Requirements
- Java 11 or later
- Maven or Gradle
- 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)
Maven Setup
Add to your pom.xml:
<dependency>
<groupId>ai.opportify</groupId>
<artifactId>opportify-sdk-java</artifactId>
<version>0.6.1</version>
</dependency>
Gradle Setup
Add to your build.gradle:
dependencies {
implementation 'ai.opportify:opportify-sdk-java:0.6.1'
}
Quick start: single email analysis
import ai.opportify.client.ApiClient;
import ai.opportify.client.Configuration;
import ai.opportify.client.auth.ApiKeyAuth;
import ai.opportify.client.api.EmailInsightsApi;
import ai.opportify.client.model.AnalyzeEmailRequest;
import ai.opportify.client.model.EmailAnalysisResponse;
public class EmailValidationExample {
public static void main(String[] args) {
// Configure API client
ApiClient defaultClient = Configuration.getDefaultApiClient();
defaultClient.setBasePath("https://api.opportify.ai/insights/v1");
// Set API key authentication
ApiKeyAuth opportifyToken = (ApiKeyAuth) defaultClient.getAuthentication("opportifyToken");
opportifyToken.setApiKey(System.getenv("OPPORTIFY_API_KEY"));
// Create API instance
EmailInsightsApi apiInstance = new EmailInsightsApi(defaultClient);
// Prepare request
AnalyzeEmailRequest request = new AnalyzeEmailRequest();
request.setEmail("user@example.com");
request.setEnableAI(true);
request.setEnableAutoCorrection(true);
try {
EmailAnalysisResponse result = apiInstance.analyzeEmail(request);
System.out.println("Email: " + result.getEmailAddress());
System.out.println("Deliverable: " + result.getIsDeliverable());
System.out.println("Risk Score: " + result.getRiskReport().getScore());
System.out.println("Risk Level: " + result.getRiskReport().getLevel());
System.out.println("Provider: " + result.getEmailProvider());
System.out.println("Catch-All: " + result.getIsCatchAll());
System.out.println("Mailbox Full: " + result.getIsMailboxFull());
} catch (Exception e) {
System.err.println("Email analysis failed: " + e.getMessage());
e.printStackTrace();
}
}
}
When to enable AI and autocorrection
setEnableAutoCorrection(true)helps correct common user typos in form inputs (e.g., "gmial.com" → "gmail.com").setEnableAI(true)adds domain enrichment and risk signals that improve decisions for gating, segmentation, and fraud protection.
Batch analysis with polling
Use batch jobs for processing large email lists. A job returns a jobId that you can poll until results are ready.
import ai.opportify.client.api.EmailInsightsApi;
import ai.opportify.client.model.BatchAnalyzeRequest;
import ai.opportify.client.model.BatchJobResponse;
import ai.opportify.client.model.BatchStatusResponse;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
public class BatchEmailValidator {
private final EmailInsightsApi apiInstance;
private static final int MAX_WAIT_SECONDS = 600;
private static final int INITIAL_DELAY_MS = 2000;
public BatchEmailValidator(EmailInsightsApi apiInstance) {
this.apiInstance = apiInstance;
}
public void processBatch() {
// Prepare batch request
BatchAnalyzeRequest request = new BatchAnalyzeRequest();
request.setEmails(Arrays.asList(
"user1@company.com",
"user2@domain.org",
"test@example.com"
));
request.setName("User Signup Validation");
request.setEnableAI(true);
request.setEnableAutoCorrection(true);
try {
// Start batch job
BatchJobResponse batchJob = apiInstance.batchAnalyzeEmails(request);
String jobId = batchJob.getJobId();
System.out.println("Batch job started: " + jobId);
// Wait for completion
BatchStatusResponse finalStatus = waitForCompletion(jobId);
System.out.println("Batch completed successfully!");
System.out.println("CSV Download: " + finalStatus.getDownloadUrls().getCsv());
System.out.println("JSON Download: " + finalStatus.getDownloadUrls().getJson());
} catch (Exception e) {
System.err.println("Batch processing failed: " + e.getMessage());
e.printStackTrace();
}
}
private BatchStatusResponse waitForCompletion(String jobId) throws Exception {
long startTime = System.currentTimeMillis();
long delay = INITIAL_DELAY_MS;
while (true) {
BatchStatusResponse status = apiInstance.getBatchStatus(jobId);
String state = status.getStatus();
Integer progress = status.getProgress();
System.out.println("Status: " + state + " - Progress: " + progress + "%");
if ("COMPLETED".equals(state)) {
return status;
}
if ("ERROR".equals(state)) {
String description = status.getStatusDescription() != null
? status.getStatusDescription()
: "Unknown error";
throw new RuntimeException("Batch job failed: " + description);
}
long elapsed = System.currentTimeMillis() - startTime;
if (elapsed > MAX_WAIT_SECONDS * 1000) {
throw new RuntimeException("Batch job timeout - did not complete within time limit");
}
// Exponential backoff with cap at 30 seconds
Thread.sleep(Math.min(delay, 30000));
delay = (long) Math.min(delay * 1.6, 30000);
}
}
}
Batch with file upload
import java.io.File;
public class FileUploadExample {
private final EmailInsightsApi apiInstance;
private static final int MAX_WAIT_SECONDS = 600;
private static final int INITIAL_DELAY_MS = 2000;
public FileUploadExample(EmailInsightsApi apiInstance) {
this.apiInstance = apiInstance;
}
public void uploadAndProcess() {
File csvFile = new File("./emails.csv");
BatchAnalyzeRequest request = new BatchAnalyzeRequest();
request.setName("Monthly Email List Cleanup");
request.setEnableAI(true);
request.setEnableAutoCorrection(true);
try {
BatchJobResponse batchJob = apiInstance.batchAnalyzeEmailsFromFile(csvFile, request);
System.out.println("File uploaded, job ID: " + batchJob.getJobId());
// Poll for results
BatchStatusResponse result = waitForCompletion(batchJob.getJobId());
System.out.println("Processing complete!");
} catch (Exception e) {
System.err.println("File upload failed: " + e.getMessage());
e.printStackTrace();
}
}
private BatchStatusResponse waitForCompletion(String jobId) throws Exception {
long startTime = System.currentTimeMillis();
long delay = INITIAL_DELAY_MS;
while (true) {
BatchStatusResponse status = apiInstance.getBatchStatus(jobId);
String state = status.getStatus();
if ("COMPLETED".equals(state)) {
return status;
}
if ("ERROR".equals(state)) {
throw new RuntimeException("Batch job failed: " + status.getStatusDescription());
}
long elapsed = System.currentTimeMillis() - startTime;
if (elapsed > MAX_WAIT_SECONDS * 1000) {
throw new RuntimeException("Batch job timeout");
}
Thread.sleep(Math.min(delay, 30000));
delay = (long) Math.min(delay * 1.6, 30000);
}
}
}
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 using CompletableFuture or reactive patterns for async batch processing.
Spring Boot integration
Here's a complete example showing how to integrate Email Insights in a Spring Boot application with caching and error handling:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import ai.opportify.client.ApiClient;
import ai.opportify.client.Configuration;
import ai.opportify.client.auth.ApiKeyAuth;
import ai.opportify.client.api.EmailInsightsApi;
import ai.opportify.client.model.AnalyzeEmailRequest;
import ai.opportify.client.model.EmailAnalysisResponse;
@Service
public class EmailValidationService {
private final EmailInsightsApi apiInstance;
public EmailValidationService(@Value("${opportify.api.key}") String apiKey) {
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://api.opportify.ai/insights/v1");
ApiKeyAuth auth = (ApiKeyAuth) client.getAuthentication("opportifyToken");
auth.setApiKey(apiKey);
this.apiInstance = new EmailInsightsApi(client);
}
@Cacheable(value = "emailValidations", key = "#email", unless = "#result == null")
public EmailAnalysisResponse validateEmail(String email) {
AnalyzeEmailRequest request = new AnalyzeEmailRequest();
request.setEmail(email);
request.setEnableAI(true);
request.setEnableAutoCorrection(true);
try {
return apiInstance.analyzeEmail(request);
} catch (Exception e) {
throw new EmailValidationException("Failed to validate email: " + email, e);
}
}
public boolean isAcceptable(EmailAnalysisResponse result, ValidationCriteria criteria) {
if (!"yes".equalsIgnoreCase(result.getIsDeliverable())) {
return false;
}
Integer riskScore = result.getRiskReport() != null
? result.getRiskReport().getScore()
: null;
if (riskScore != null && riskScore > criteria.getMaxRiskScore()) {
return false;
}
if (!criteria.isAllowDisposable() && "disposable".equals(result.getEmailType())) {
return false;
}
if (!criteria.isAllowRole() &&
result.getAddressSignals() != null &&
Boolean.TRUE.equals(result.getAddressSignals().getIsRoleAddress())) {
return false;
}
return true;
}
}
// Supporting classes (create as separate files in production)
// ValidationCriteria.java
public class ValidationCriteria {
private int maxRiskScore = 600;
private boolean allowDisposable = false;
private boolean allowRole = true;
// Getters and setters
public int getMaxRiskScore() { return maxRiskScore; }
public void setMaxRiskScore(int maxRiskScore) { this.maxRiskScore = maxRiskScore; }
public boolean isAllowDisposable() { return allowDisposable; }
public void setAllowDisposable(boolean allowDisposable) { this.allowDisposable = allowDisposable; }
public boolean isAllowRole() { return allowRole; }
public void setAllowRole(boolean allowRole) { this.allowRole = allowRole; }
}
// EmailValidationException.java
public class EmailValidationException extends RuntimeException {
public EmailValidationException(String message, Throwable cause) {
super(message, cause);
}
}
Spring Boot REST Controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@RestController
@RequestMapping("/api")
public class SignupController {
private static final Logger logger = LoggerFactory.getLogger(SignupController.class);
@Autowired
private EmailValidationService emailValidationService;
@PostMapping("/signup")
public ResponseEntity<?> signup(@RequestBody SignupRequest request) {
try {
// Validate email
EmailAnalysisResponse validation = emailValidationService.validateEmail(request.getEmail());
// Check acceptance criteria
ValidationCriteria criteria = new ValidationCriteria();
criteria.setMaxRiskScore(600);
criteria.setAllowDisposable(false);
criteria.setAllowRole(false);
if (!emailValidationService.isAcceptable(validation, criteria)) {
return ResponseEntity.badRequest().body(new ErrorResponse(
"Invalid email address",
Map.of(
"riskScore", validation.getRiskReport().getScore(),
"deliverable", validation.getIsDeliverable(),
"emailType", validation.getEmailType()
)
));
}
// Proceed with registration
User user = createUser(request.getEmail(), request.getName());
return ResponseEntity.ok(new SignupResponse(true, user.getId()));
} catch (EmailValidationException e) {
// Graceful degradation - don't block signup on validation failure
logger.error("Email validation failed", e);
return ResponseEntity.ok(new SignupResponse(
true,
null,
"Email validation temporarily unavailable"
));
}
}
}
Handling errors safely
Always handle exceptions appropriately for different error scenarios:
import ai.opportify.client.ApiException;
public class SafeEmailValidator {
private final EmailInsightsApi apiInstance;
public SafeEmailValidator(EmailInsightsApi apiInstance) {
this.apiInstance = apiInstance;
}
public EmailAnalysisResponse analyzeWithErrorHandling(String email) {
AnalyzeEmailRequest request = new AnalyzeEmailRequest();
request.setEmail(email);
request.setEnableAI(true);
request.setEnableAutoCorrection(true);
try {
return apiInstance.analyzeEmail(request);
} catch (ApiException e) {
int statusCode = e.getCode();
String responseBody = e.getResponseBody();
if (statusCode == 403) {
throw new RuntimeException("API key invalid or feature not available on your plan");
} else if (statusCode == 429) {
throw new RuntimeException("Rate limit exceeded - implement backoff and retry");
} else if (statusCode >= 500) {
throw new RuntimeException("Server error - retry with exponential backoff");
} else {
throw new RuntimeException("API error: " + responseBody, e);
}
} catch (Exception e) {
throw new RuntimeException("Unexpected error during email validation", e);
}
}
// Retry with exponential backoff
public EmailAnalysisResponse analyzeWithRetry(String email, int maxRetries) {
Exception lastException = null;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
return analyzeWithErrorHandling(email);
} catch (Exception e) {
lastException = e;
// Don't retry on client errors (4xx)
if (e instanceof ApiException) {
ApiException apiEx = (ApiException) e;
if (apiEx.getCode() >= 400 && apiEx.getCode() < 500) {
throw e;
}
}
// Exponential backoff for retries
if (attempt < maxRetries) {
try {
long delay = Math.min(1000 * (long) Math.pow(2, attempt), 10000);
Thread.sleep(delay);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Retry interrupted", ie);
}
}
}
}
throw new RuntimeException("Max retries exceeded", lastException);
}
}
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 risk score 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)
public enum ContactAction {
ACCEPT, REVALIDATE, REVIEW, REJECT
}
public class EmailClassifier {
public static ContactAction classifyContact(EmailAnalysisResponse result) {
String deliverable = result.getIsDeliverable() != null
? result.getIsDeliverable().toLowerCase()
: "";
String riskLevel = result.getRiskReport() != null
? result.getRiskReport().getLevel().toLowerCase()
: "";
boolean isCatchAll = Boolean.TRUE.equals(result.getIsCatchAll());
boolean isMailboxFull = Boolean.TRUE.equals(result.getIsMailboxFull());
boolean isReachable = Boolean.TRUE.equals(result.getIsReachable());
boolean isDisposable = "disposable".equals(result.getEmailType());
// Immediate rejection criteria
if (isDisposable || "highest".equals(riskLevel)) {
return ContactAction.REJECT;
}
// Handle non-deliverable emails
if ("no".equals(deliverable)) {
return isMailboxFull ? ContactAction.REVALIDATE : ContactAction.REJECT;
}
// Handle unknown deliverability
if ("unknown".equals(deliverable)) {
if ("high".equals(riskLevel)) return ContactAction.REVIEW;
if (isCatchAll || !isReachable) return ContactAction.REVALIDATE;
return ContactAction.REVIEW;
}
// Handle deliverable emails (yes)
if ("high".equals(riskLevel)) return ContactAction.REVIEW;
if ("medium".equals(riskLevel) || isCatchAll) return ContactAction.REVALIDATE;
return ContactAction.ACCEPT;
}
}
// Usage example
EmailAnalysisResponse result = apiInstance.analyzeEmail(request);
ContactAction action = EmailClassifier.classifyContact(result);
System.out.println("Action for " + result.getEmailAddress() + ": " + action);
Production hardening
Configuration management
Store your API key securely using environment variables or a secrets manager:
# application.properties
opportify.api.key=${OPPORTIFY_API_KEY}
opportify.api.base-path=https://api.opportify.ai/insights/v1
opportify.cache.ttl=3600
@Configuration
public class OpportifyConfig {
@Bean
public EmailInsightsApi emailInsightsApi(
@Value("${opportify.api.key}") String apiKey,
@Value("${opportify.api.base-path}") String basePath) {
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath(basePath);
ApiKeyAuth auth = (ApiKeyAuth) client.getAuthentication("opportifyToken");
auth.setApiKey(apiKey);
return new EmailInsightsApi(client);
}
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("emailValidations")
));
return cacheManager;
}
}
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.
Observability and monitoring
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
@Service
public class MonitoredEmailValidator {
private static final Logger logger = LoggerFactory.getLogger(MonitoredEmailValidator.class);
private final EmailInsightsApi apiInstance;
private final MeterRegistry meterRegistry;
public MonitoredEmailValidator(EmailInsightsApi apiInstance, MeterRegistry meterRegistry) {
this.apiInstance = apiInstance;
this.meterRegistry = meterRegistry;
}
public EmailAnalysisResponse validate(String email) {
Timer.Sample sample = Timer.start(meterRegistry);
try {
AnalyzeEmailRequest request = new AnalyzeEmailRequest();
request.setEmail(email);
request.setEnableAI(true);
request.setEnableAutoCorrection(true);
EmailAnalysisResponse result = apiInstance.analyzeEmail(request);
sample.stop(Timer.builder("email.validation")
.tag("status", "success")
.tag("risk_level", result.getRiskReport().getLevel())
.register(meterRegistry));
logger.info("Email validation successful - Risk: {}, Deliverable: {}",
result.getRiskReport().getLevel(),
result.getIsDeliverable());
meterRegistry.counter("email.validation.total",
"deliverable", result.getIsDeliverable()).increment();
return result;
} catch (Exception e) {
sample.stop(Timer.builder("email.validation")
.tag("status", "error")
.register(meterRegistry));
meterRegistry.counter("email.validation.errors").increment();
logger.error("Email validation failed", e);
throw new EmailValidationException("Validation failed for email", e);
}
}
}
Local testing checklist
- Use synthetic emails covering edge cases: disposable domains, catch-all domains, corporate domains, common typos.
- Test routing logic for each risk level and deliverability value 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 with Spring Boot's @Cacheable.
- Test retry logic and exponential backoff.
Where to go next
SDK & Documentation:
- Java 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: Main SDK classes and methods
EmailInsightsApi- Main API client classAnalyzeEmailRequest- Single email analysis requestBatchAnalyzeRequest- Batch analysis requestEmailAnalysisResponse- Analysis result with risk score and signalsBatchJobResponse- Batch job creation response with jobIdBatchStatusResponse- Batch job status and download URLsapiInstance.analyzeEmail(request)apiInstance.batchAnalyzeEmails(request)apiInstance.batchAnalyzeEmailsFromFile(file, request)apiInstance.getBatchStatus(jobId)