Integration · Salesforce

Salesforce email validation

An Email field tells you what someone typed. Whether that mailbox exists is a question for the receiving mail server. Here are three ways to ask it automatically and store the answer on the lead.

Updated October 10, 2026 · 8 min read

Salesforce email validation is more than a format rule on the Email field. A real check asks the recipient’s mail server whether the mailbox exists, and flags disposable and role addresses on the way. This guide connects Salesforce to our email validation API so every new lead gets that check and keeps the answer in a field.

There are three working setups. Pick by who maintains your org:

SetupWho builds itCodeNotes
Flow HTTP CalloutAdminNoneNamed credential plus a record-triggered flow
Apex QueueableDeveloperApexFull control, chunking, tests
ZapierAnyoneNoneSalesforce is a Premium app on Zapier: paid Zapier plan

What you get

Each check returns, among other fields:

Create two text fields on Lead (and Contact, if you check those too), for example Email State (Email_State__c) and Email Reason (Email_Reason__c). Reports, list views and campaign filters can then exclude undeliverable leads. unknown means the mail server gave no clear answer; treat it as unchecked, not as bad.

Store the API key in a named credential

Both the Flow and the Apex setup send the key from a named credential, so it never appears in a flow or in code. This follows Salesforce’s pattern for custom headers and API keys:

  1. In Setup, open Named Credentials, switch to External Credentials and create one named EmailValidationAuth. Set Authentication Protocol to Custom.
  2. Under Principals, click New. Use the Named Principal identity type and add an authentication parameter named ApiKey whose value is your emailvalidation.io API key.
  3. Under Custom Headers, click New. Name: apikey. Value: {!$Credential.EmailValidationAuth.ApiKey}.
  4. Create the named credential EmailValidation with the URL https://api.emailvalidation.io, select the external credential, turn off Generate Authorization Header and turn on Allow Formulas in HTTP Header.
  5. Map the principal to a permission set and assign it to every user whose record changes start the check, including integration users. Without that access, the callout fails for that user.
  6. If that permission set is a custom one, also give it access to the User External Credentials object (in the permission set, Object Settings > User External Credentials). Salesforce requires this for every user who makes an authenticated callout; most standard permission sets already have it.

Salesforce email validation with a Flow HTTP Callout

  1. Create a record-triggered flow on Lead that runs when a record is created (or created or updated, if you want to re-check changed addresses).

  2. Optimize it for actions and related records and add a Run Asynchronously path. Salesforce describes this path as the place for “sending requests to external systems”; it runs after the lead is saved and doesn’t slow down the save.

  3. On that path, add an Action and click Create HTTP Callout. Name the external service and select the EmailValidation named credential.

  4. Configure the invocable action: Method GET, URL path /v1/info, and a query parameter email of type String.

  5. Provide a sample response so Flow can build the output data structure, for example this one, taken from the documented API response:

    {
      "email": "support@emailvalidation.io",
      "user": "support",
      "tag": "",
      "domain": "emailvalidation.io",
      "format_valid": true,
      "mx_found": true,
      "smtp_check": true,
      "role": true,
      "disposable": false,
      "free": false,
      "state": "deliverable",
      "reason": "valid_mailbox",
      "did_you_mean": ""
    }
    

    HTTP Callout doesn’t support float and long types in responses (Salesforce suggests integer or double instead), and catch_all is null unless you request it, so this sample leaves out score and catch_all. The flow below doesn’t need them.

  6. Save the action. In the action’s settings, set email to the triggering lead’s Email ({!$Record.Email}).

  7. Add an Update Records element for the triggering lead and set Email State to the action’s 2XX output state and Email Reason to reason.

  8. Add a fault path to the action. Flow returns every response with status 300 or higher as an error, for example a 429 when your monthly quota is used up; on the fault path, leave the lead unchecked or set Email State to unknown.

  9. Debug with a test lead, then Activate the flow.

To limit the check to new addresses on updates, add an entry condition that the Email field is changed.

Setup in steps: Apex callout

For developers who prefer code, a trigger hands new or changed leads to a Queueable job. It calls the API through the same named credential, so you don’t need a Remote Site Setting for api.emailvalidation.io; if you call the URL directly instead, add one. Callouts can’t run in the trigger itself while the insert is pending, and a Queueable class that implements Database.AllowsCallouts may make them. Callout time in one Apex transaction adds up to at most 120 seconds, so the job checks ten leads and chains a new job for the rest.

public with sharing class LeadEmailValidationJob implements Queueable, Database.AllowsCallouts {
    // Callout time in one transaction is capped at 120 seconds: check a few leads per job.
    private static final Integer CHECKS_PER_JOB = 10;
    private final List<Id> leadIds;

    public LeadEmailValidationJob(List<Id> leadIds) {
        this.leadIds = leadIds;
    }

    public void execute(QueueableContext context) {
        List<Id> batchIds = new List<Id>();
        List<Id> remaining = new List<Id>();
        for (Id leadId : leadIds) {
            if (batchIds.size() < CHECKS_PER_JOB) {
                batchIds.add(leadId);
            } else {
                remaining.add(leadId);
            }
        }

        List<Lead> updates = new List<Lead>();
        for (Lead l : [SELECT Id, Email FROM Lead WHERE Id IN :batchIds AND Email != null]) {
            HttpRequest req = new HttpRequest();
            // The named credential adds the apikey header; the key is not in the code.
            req.setEndpoint('callout:EmailValidation/v1/info?email=' + EncodingUtil.urlEncode(l.Email, 'UTF-8'));
            req.setMethod('GET');
            req.setTimeout(10000);
            HttpResponse res;
            try {
                res = new Http().send(req);
            } catch (CalloutException e) {
                continue; // timeout or network error: leave this lead unchecked
            }
            if (res.getStatusCode() != 200) {
                continue; // 401, 422, 429: leave this lead unchecked
            }
            Map<String, Object> result = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
            updates.add(new Lead(
                Id = l.Id,
                Email_State__c = (String) result.get('state'),
                Email_Reason__c = (String) result.get('reason')
            ));
        }

        // DML only after all callouts: no callout may follow pending database work.
        if (!updates.isEmpty()) {
            update updates;
        }
        if (!remaining.isEmpty()) {
            System.enqueueJob(new LeadEmailValidationJob(remaining));
        }
    }
}
trigger LeadEmailValidation on Lead (after insert, after update) {
    List<Id> leadIds = new List<Id>();
    for (Lead l : Trigger.new) {
        Boolean emailChanged = Trigger.isInsert || l.Email != Trigger.oldMap.get(l.Id).Email;
        if (l.Email != null && emailChanged) {
            leadIds.add(l.Id);
        }
    }
    if (!leadIds.isEmpty()) {
        System.enqueueJob(new LeadEmailValidationJob(leadIds));
    }
}

The job’s own update doesn’t change the Email field, so it doesn’t enqueue itself again. In Developer Edition and trial orgs, a chain of Queueable jobs is limited to five, so test bulk imports there with 50 leads or fewer. Production deployments need test coverage; tests can’t reach the API, so use a mock with Test.setMock(HttpCalloutMock.class, …):

@IsTest
private class LeadEmailValidationJobTest {
    private class ApiMock implements HttpCalloutMock {
        public HttpResponse respond(HttpRequest req) {
            HttpResponse res = new HttpResponse();
            res.setStatusCode(200);
            res.setHeader('Content-Type', 'application/json');
            res.setBody('{"state":"undeliverable","reason":"invalid_mailbox"}');
            return res;
        }
    }

    @IsTest
    static void storesStateAndReason() {
        Test.setMock(HttpCalloutMock.class, new ApiMock());
        Test.startTest();
        insert new Lead(LastName = 'Doe', Company = 'Example Corp', Email = 'jane@example.org');
        Test.stopTest();

        Lead l = [SELECT Email_State__c, Email_Reason__c FROM Lead LIMIT 1];
        System.assertEquals('undeliverable', l.Email_State__c);
        System.assertEquals('invalid_mailbox', l.Email_Reason__c);
    }
}

Setup in steps: Zapier

  1. Connect your emailvalidation.io account in Zapier (see the Zapier integration).
  2. Start from the template “Verify new Salesforce lead email addresses with Emailvalidation”, or build a Zap with the Salesforce trigger New Lead.
  3. Add the Emailvalidation action Verify E-Mail Address and map the lead’s Email field.
  4. Test the step to see the fields it returns, then add the Salesforce action Update Lead and write the result to Email State.

Salesforce is marked Premium on Zapier, and Premium apps need a paid Zapier plan (Zapier pricing, checked October 2026).

Example workflow

A sales team imports leads from webinars and a web-to-lead form:

  1. A lead is created; the flow’s Run Asynchronously path checks the address.
  2. Email State becomes undeliverable with the reason invalid_mailbox.
  3. A list view “Leads to fix” shows every lead with that state, so reps correct the address or disqualify the lead before it enters a cadence.
  4. Campaign and email list filters exclude undeliverable, which keeps those addresses from bouncing (see hard vs. soft bounce).

For the leads you already have, export them to CSV and send the file to the bulk endpoint. The result CSV keeps your original columns, including the record Id if you exported it, and adds 15 emailvalidation_* columns that you can import back into your fields. Teams that would rather hand over the file use the email list cleaning service.

Pricing note

Each check costs 1 credit; only successful calls count. In bulk jobs, duplicates and invalid rows are free, and addresses that time out are refunded when the job completes. The free plan includes 100 credits a month; Small is $9.99 a month for 5,000, Medium $34.99 for 25,000 and Large $94.99 for 100,000, excluding VAT (email validation API pricing). Paid plans have no per-minute limit; the free plan allows 10 requests a minute, which a large lead import can exceed.

Sources

Checked October 10, 2026: Salesforce Help, Custom headers and API keys for named credentials and Enable user external credentials; Salesforce Help, HTTP Callout in Flow Builder, Configure an HTTP Callout action and HTTP Callout considerations and limits; Trailhead, Build the HTTP Callout flow and Get started with triggered flows; Apex Developer Guide, Callout limits and limitations, Named credentials as callout endpoints, Queueable Apex, HttpCalloutMock and EncodingUtil; Zapier, Emailvalidation + Salesforce, Salesforce integrations and pricing.

Email validation in other apps

Frequently asked questions

How do I validate email addresses in Salesforce?

Call an email validation API when a lead or contact is created and store the result in a custom field. Admins can do this without code using a Flow HTTP Callout; developers can use an Apex callout from a Queueable job; teams on a paid Zapier plan can use the Emailvalidation app’s Verify E-Mail Address action.

Can I verify email addresses in Salesforce without Apex?

Yes. Flow Builder’s HTTP Callout action generates the callout for you from a named credential and a sample response. Salesforce lets admins with the Customize Applications permission create it.

Why does the callout have to run asynchronously?

Apex can’t make a callout while the same transaction has pending database work, such as the insert that created the lead. In a record-triggered flow, put the callout on the Run Asynchronously path, which runs after the record is saved; in Apex, use a Queueable class that implements Database.AllowsCallouts.

How do I check the leads I already have?

Export the leads to a CSV file, upload it to the bulk endpoint of the API or our list cleaning service, and import the result columns back into your custom fields. Duplicates and invalid rows in a bulk job are free.

Email validation API

Validate emails in your app

emailvalidation.io checks syntax, MX records and the mailbox over SMTP, flags disposable, role and free addresses and returns a quality score, in one request.

/v1/info Email validation API Read the documentation

100 free validations every month. No credit card required.

GET https://api.emailvalidation.io/v1/info?email=support@emailvalidation.io

{
  "email": "support@emailvalidation.io",
  "user": "support",
  "tag": "",
  "domain": "emailvalidation.io",
  "format_valid": true,
  "mx_found": true,
  "smtp_check": true,
  "catch_all": null,
  "role": true,
  "disposable": false,
  "free": false,
  "score": 0.64,
  "state": "deliverable",
  "reason": "valid_mailbox",
  "did_you_mean": ""
}

Keep reading

Start using our email validation software today!

Get 100 validations per month for free