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:
| Setup | Who builds it | Code | Notes |
|---|---|---|---|
| Flow HTTP Callout | Admin | None | Named credential plus a record-triggered flow |
| Apex Queueable | Developer | Apex | Full control, chunking, tests |
| Zapier | Anyone | None | Salesforce is a Premium app on Zapier: paid Zapier plan |
What you get
Each check returns, among other fields:
state:deliverable,undeliverableorunknownreason: why, for exampleinvalid_mailbox(the server rejected the mailbox),invalid_mx(the domain has no mail server) orlow_deliverability(a catch-all domain that accepts every address)disposable,roleandfree:trueorfalsedid_you_mean: a suggested correction for a likely typo, or empty
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:
- In Setup, open Named Credentials, switch to External Credentials and create one named
EmailValidationAuth. Set Authentication Protocol to Custom. - Under Principals, click New. Use the Named Principal identity type and add an authentication parameter named
ApiKeywhose value is your emailvalidation.io API key. - Under Custom Headers, click New. Name:
apikey. Value:{!.$Credential .Email Validation Auth .Api Key} - Create the named credential
EmailValidationwith the URLhttps://api, select the external credential, turn off Generate Authorization Header and turn on Allow Formulas in HTTP Header..emailvalidation.io - 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.
- 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
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).
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.
On that path, add an Action and click Create HTTP Callout. Name the external service and select the
EmailValidationnamed credential.Configure the invocable action: Method GET, URL path
/v1/info, and a query parameteremailof type String.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_allisnullunless you request it, so this sample leaves outscoreandcatch_all. The flow below doesn’t need them.Save the action. In the action’s settings, set
emailto the triggering lead’s Email ({!$Record.Email}).Add an Update Records element for the triggering lead and set Email State to the action’s 2XX output
stateand Email Reason toreason.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.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:
@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
- Connect your emailvalidation.io account in Zapier (see the Zapier integration).
- Start from the template “Verify new Salesforce lead email addresses with Emailvalidation”, or build a Zap with the Salesforce trigger New Lead.
- Add the Emailvalidation action Verify E-Mail Address and map the lead’s Email field.
- 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:
- A lead is created; the flow’s Run Asynchronously path checks the address.
- Email State becomes
undeliverablewith the reasoninvalid_mailbox. - 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.
- 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.