Integrate Email Insights into Your Java Application: A Developer's Guide

11 min readOpportify Team

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:

Requirements

  • Java 11 or later
  • Maven or Gradle
  • A valid API key (see below for instructions)

Get Your API Key

  1. Log into your Opportify dashboard
  2. Navigate to Access Control → API Keys
  3. Generate a new API key or copy your existing key
  4. 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 jobId in 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:

Product & Features:

Security & Compliance:

Reference: Main SDK classes and methods

  • EmailInsightsApi - Main API client class
  • AnalyzeEmailRequest - Single email analysis request
  • BatchAnalyzeRequest - Batch analysis request
  • EmailAnalysisResponse - Analysis result with risk score and signals
  • BatchJobResponse - Batch job creation response with jobId
  • BatchStatusResponse - Batch job status and download URLs
  • apiInstance.analyzeEmail(request)
  • apiInstance.batchAnalyzeEmails(request)
  • apiInstance.batchAnalyzeEmailsFromFile(file, request)
  • apiInstance.getBatchStatus(jobId)