Key Takeaways

  • Salesforce ships built-in user email confirmation but no native lead-quality validation, leaving every Lead, Contact, and custom Email field exposed to invalid data.
  • Apex callouts to EmailVerifierAPI from a Lead trigger let you validate addresses before the record is committed, blocking disposable, role-based, and invalid contacts at the source.
  • The implementation uses Named Credentials for the API key and runs as an asynchronous future callout to keep the trigger DML-safe and bulk-friendly.
  • The v2 endpoint returns a status of passed, failed, unknown, or transient plus structured flags for disposable, role-based, gibberish, and offensive addresses.

Salesforce orgs accumulate dirty email data faster than almost any other system. Web-to-Lead forms accept anything, marketing imports skip validation, and integrations from third-party tools dump unverified addresses straight into the Lead and Contact objects. The native email-verification feature in Salesforce only confirms addresses for User records that need to send mail through the platform. It does nothing for the lead and contact data that drives revenue.

This guide walks through a production-ready pattern for verifying email addresses on Salesforce records using Apex callouts to the email validation API documentation. The pattern handles Web-to-Lead, manual entry, and bulk import paths, and stays within governor limits even on multi-thousand-record imports.

Architecture: Named Credential, Service Class, Future Callout

The implementation has three layers. A Named Credential stores the API endpoint and key so credentials never appear in code. A service class handles the HTTP callout, JSON deserialization, and error handling. A future-annotated invocation from the trigger keeps DML and callouts properly separated.

Configure the Named Credential first. In Setup, create a new Named Credential pointing to the verification endpoint with a custom header carrying the API key, or pass the key as a query parameter through a credential parameter. The trigger and service class then reference the credential rather than hardcoding the URL.

Test the Endpoint With cURL First

Before writing Apex, confirm the endpoint works against a known address from any shell. The v2 verify endpoint accepts the email as a query parameter and returns a JSON document.

curl -X GET 
  "https://emailverifierapi.com/v2/verify?api_key=YOUR_API_KEY&email=jane@example.com"

# Response
{
  "email": "jane@example.com",
  "status": "passed",
  "sub_status": "mailboxExists",
  "isDisposable": false,
  "isFreeService": false,
  "isOffensive": false,
  "isRoleAccount": false,
  "isGibberish": false,
  "smtp_check": "success"
}

The status field is the primary decision input. A value of passed indicates the mailbox exists and accepts mail. Failed means the mailbox does not exist, the syntax is invalid, the domain has no MX server, or the address is otherwise unrouteable. Unknown covers greylisting and transient SMTP errors that warrant retry. Transient indicates a temporary issue worth deferring.

The Apex Service Class

The service class wraps the HTTP callout, parses the JSON response, and exposes a typed result. The verify email with Apex integration page documents the full class with field mappings and bulk patterns.

public with sharing class EmailVerifier {

    public class VerifyResult {
        public String email;
        public String status;
        public String sub_status;
        public Boolean isDisposable;
        public Boolean isFreeService;
        public Boolean isRoleAccount;
        public Boolean isGibberish;
        public Boolean isOffensive;
        public String smtp_check;
    }

    public class VerifyException extends Exception {}

    public static VerifyResult verify(String emailAddress) {
        Http http = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint(
            'callout:EmailVerifierAPI/v2/verify?email=' +
            EncodingUtil.urlEncode(emailAddress, 'UTF-8')
        );
        req.setMethod('GET');
        req.setTimeout(20000);

        HttpResponse res = http.send(req);
        if (res.getStatusCode() != 200) {
            throw new VerifyException(
                'API returned ' + res.getStatusCode() + ': ' + res.getBody()
            );
        }
        return (VerifyResult) JSON.deserialize(res.getBody(), VerifyResult.class);
    }

    @future(callout=true)
    public static void verifyLeadsAsync(Set<Id> leadIds) {
        List<Lead> leads = [
            SELECT Id, Email, Email_Verification_Status__c, Email_Is_Disposable__c
            FROM Lead WHERE Id IN :leadIds AND Email != null
        ];
        List<Lead> toUpdate = new List<Lead>();
        for (Lead l : leads) {
            try {
                VerifyResult r = verify(l.Email);
                l.Email_Verification_Status__c = r.status;
                l.Email_Is_Disposable__c = r.isDisposable;
                toUpdate.add(l);
            } catch (Exception e) {
                System.debug(LoggingLevel.WARN,
                    'Verify failed for ' + l.Id + ': ' + e.getMessage());
            }
        }
        if (!toUpdate.isEmpty()) update toUpdate;
    }
}

The class uses a Named Credential reference (callout:EmailVerifierAPI) so the API key never appears in code. The future annotation makes the callout asynchronous and bulk-safe. Custom fields on the Lead object capture the verification status and the disposable flag for use in routing rules and reports.

Wiring It Into a Lead Trigger

The trigger collects new and updated lead IDs whose Email field changed and dispatches them to the future method. This avoids running a callout for every record on a bulk import while still validating every relevant address.

trigger LeadEmailVerifyTrigger on Lead (after insert, after update) {
    Set<Id> toVerify = new Set<Id>();

    if (Trigger.isInsert) {
        for (Lead l : Trigger.new) {
            if (String.isNotBlank(l.Email)) toVerify.add(l.Id);
        }
    } else if (Trigger.isUpdate) {
        for (Lead l : Trigger.new) {
            Lead old = Trigger.oldMap.get(l.Id);
            if (l.Email != old.Email && String.isNotBlank(l.Email)) {
                toVerify.add(l.Id);
            }
        }
    }

    if (!toVerify.isEmpty() && !System.isFuture() && !System.isBatch()) {
        EmailVerifier.verifyLeadsAsync(toVerify);
    }
}
Pro Tip Use isFuture and isBatch guards to prevent recursive callout chains when batch jobs or other future methods update Leads. Without them, a downstream automation that touches the Email field can re-enqueue verification work indefinitely.

Acting on the Verification Result

Once the verification status lands on the record, downstream automation can route, score, or block leads based on the value. A typical configuration uses Salesforce Flow to assign failed-status leads to a low-priority queue, suppress disposable addresses from marketing campaigns, and flag role accounts for sales review rather than automated outbound.

The richer the response, the more targeted the routing. A passed status with isFreeService true is a real lead but probably consumer rather than enterprise. A failed status with sub_status of isCatchall is a domain that accepts everything and warrants engagement-based scoring before any send. A status of unknown with sub_status of isGreylisting deserves a retry in 60 minutes rather than rejection.

A single bad lead import can degrade Salesforce email deliverability for months. Verification at the trigger layer prevents the contamination from ever reaching marketing automation.

Bulk Imports and Governor Limits

Synchronous Apex transactions are limited to 100 callouts. The future-method pattern raises that ceiling, but very large imports (50,000+ records) still need a different approach. For those scenarios, a Queueable Apex job that processes records in chunks of 100 stays within limits while keeping verification on the same business day as the import.

Alternatively, a scheduled batch job can sweep newly imported records overnight using the same EmailVerifier class, calling the email verification API in chunks. This pattern works well for organizations doing nightly data feeds from external systems.

Test classes should mock the HTTP response using Test.setMock(HttpCalloutMock.class, ...) to verify the JSON parsing and field mapping without making real callouts during deployment. The mock response should cover all four status values to ensure the routing logic handles every case.

Production rollout typically follows a three-step sequence: deploy to sandbox with verification enabled on a single Lead Source, validate the routing behavior with sample data, then expand to all sources. 100 free email verification credits are sufficient to complete sandbox validation before committing to production volume. Email verification pricing at the per-credit level scales to multi-million record orgs without infrastructure surprises.

Frequently Asked Questions

Does Salesforce already verify email addresses on Lead and Contact records?

No. Salesforce native email verification confirms addresses for User records that need to send through the platform. Lead, Contact, and custom email fields receive no validation by default, which is why third-party API integration is needed for data quality at the lead-capture layer.

Why use a future method instead of calling the API synchronously from the trigger?

Synchronous callouts from triggers are limited and can cause partial-import failures during bulk operations. A future method moves the callout to an asynchronous transaction with its own governor context, keeping the original DML clean and bulk-safe.

How do I handle the 100-callout limit on large imports?

For imports above the future-method ceiling, use Queueable Apex with chained jobs processing 100 records per chunk. For overnight processing, a scheduled batch job is the cleanest pattern. Both approaches reuse the same EmailVerifier service class.

Can the verification result drive Salesforce Flow routing?

Yes. Once the verification status field is populated on the Lead record, standard Flow, Process Builder, and Workflow Rules can branch on the value. Failed addresses can be auto-routed to a suppression list, disposable addresses can be flagged for manual review, and passed addresses can proceed to normal lead-scoring automation.