All posts

Verify Emails in Salesforce With a Flow and HTTP Callout, No Apex

··10 min read

To verify emails in Salesforce without Apex, build a record-triggered Flow with an HTTP Callout action. Store the API key on an external credential and send it as a custom Authorization header. Put the callout on the flow's asynchronous path, because Salesforce does not allow callouts while a record is saving. Map the response into custom fields for status and reason, then use those fields in list views, assignment rules and sequences. A fault path catches errors, and a scheduled flow retries anything left blank.

This guide builds that flow for Leads, using SimpleVerifier's API as the example. The same steps work for Contacts. If you need to clean records already in your org, our guide to verifying Salesforce emails by export covers the bulk route.

What you will build

Piece Where Purpose
Custom fields Object Manager, Lead Hold status, reason, checked address and date
Permission set Setup Grants access to the credential
External credential Setup, Named Credentials Stores the API key and adds the header
Named credential Setup, Named Credentials Holds the base URL
HTTP Callout action Flow Builder The generated "Verify Email" action
Record-triggered flow Flow Builder Runs the action after a lead is saved
Scheduled flow (optional) Flow Builder Retries failed checks

Salesforce's HTTP Callout overview says the feature is powered by External Services, and that you set up authentication with named credentials before you configure the action. Admins with the Customize Applications permission can use it.

Step 1: Add the custom fields

On the Lead object, create four fields:

Label API name Type Holds
Email Status SV_Email_Status__c Picklist: valid, risky, invalid status
Email Reason SV_Email_Reason__c Text(255) reason
Checked Email SV_Checked_Email__c Email The address that was checked
Email Checked On SV_Email_Checked_On__c Date/Time When it was checked

Checked Email does two jobs. It records exactly which address the status refers to. It also stops the flow from looping, as Step 6 shows. A picklist for status keeps reports clean. If you prefer, use Text and avoid picklist maintenance.

Step 2: Create the permission set

Create a permission set called SimpleVerifier Callout. You will grant it access to the credential in Step 3, then assign it to the users whose actions start the flow. Salesforce's authentication guidelines for HTTP Callout list the order: permission set, external credential, named credential, then access to the User Credentials object.

That last part matters. The guidelines say the Named Credentials subsystem stores secret values in the User Credentials object, and users need access to it through permission sets or profiles. Add it to the same permission set.

Assign the permission set to every user, integration user and automated process that creates or edits leads. A web-to-lead form, a marketing sync and a sales rep all count. A user without access makes the callout fail.

Step 3: Create the external credential

In Setup > Named Credentials > External Credentials, click New:

Field Value
Label SimpleVerifier
Name SimpleVerifier
Authentication Protocol Custom

Then add a Principal (Salesforce's Trailhead credential unit shows this step) and map it to the permission set from Step 2. On that permission set mapping, add an Authentication Parameter:

Field Value
Name apiKey
Value sv_live_... (your key)

Now add a Custom Header on the external credential:

Field Value
Name Authorization
Value {!'Bearer ' & $Credential.SimpleVerifier.apiKey}

The guidelines describe this pattern for keys and tokens: an authentication parameter holds the secret, and a custom header with the exact name the API expects references it through $Credential. Our API expects Authorization: Bearer sv_live_.... The formula adds the Bearer prefix. Naming the external credential and the named credential the same (SimpleVerifier) avoids confusion over which API name the merge field takes.

API access needs SimpleVerifier's Pro plan ($29.99 a month) or Email + Phone ($49.99). Pay-as-you-go packs are for CSV uploads only, and a pack account's key returns 402. The API docs show how to create a key.

Step 4: Create the named credential

In Setup > Named Credentials > Named Credentials, click New:

Field Value
Label SimpleVerifier
Name SimpleVerifier
URL https://simpleverifier.com
External Credential SimpleVerifier
Generate Authorization Header Cleared
Allow Formulas in HTTP Header Selected

Per Salesforce's guidelines, clear Generate Authorization Header for key and token cases, and allow formulas when a header contains a formula. Ours does.

Step 5: Create the HTTP Callout action

Open Flow Builder and start the record-triggered flow from Step 6, or any flow. Salesforce's configuration steps go like this. Add an Action element and click Create HTTP Callout.

External service:

Field Value
Name SimpleVerifier (letters and numbers only, no spaces)
Named Credential SimpleVerifier

Invocable action:

Field Value
Label Verify Email
Method GET
URL path /api/v1/verify
Query parameter key email, type String, Required

The path must start with a slash and contain no question mark. The query key becomes an input you fill in the flow.

Sample response. Paste this, then click Review and check the data types:

{
  "email": "jane@company.com",
  "status": "valid",
  "reason": "Mailbox exists",
  "mailbox_checked": true
}

Salesforce generates an Apex class from the sample. status, reason and email become String, and mailbox_checked becomes Boolean. Salesforce's HTTP Callout considerations note that enum types aren't supported, so status arrives as text. That is fine. Its values are always one of valid, risky or invalid.

Save. The action now appears in the Actions list as Verify Email.

Why GET, and why the header lives on the credential

The same considerations page says request headers aren't supported on the action. Headers belong on the credential. That is why the key sits on the external credential. GET with a query parameter also skips the request-body setup that POST needs. The API accepts both, so GET is the shorter path in Flow.

Step 6: Build the record-triggered flow

Create a Record-Triggered Flow on Lead.

Start element:

Setting Value
Trigger A record is created or updated
Condition requirements Formula evaluates to true
Formula NOT(ISBLANK({!$Record.Email})) && {!$Record.Email} <> {!$Record.SV_Checked_Email__c}
When to run Every time a record is updated and meets the condition requirements
Optimize for Actions and Related Records
Asynchronous path On

The formula is the loop guard. A new lead with an email has a blank Checked Email, so it qualifies. After the flow writes Checked Email, the update no longer qualifies, so the flow does not run again. If a rep later edits the email, the two values differ and the address is checked again.

Why the asynchronous path

Salesforce's record-triggered flow considerations say a flow fails if it calls an external service during its synchronous run, because Salesforce prevents callouts while a record is saving. The fix they give: turn on the asynchronous path under Actions and Related Records. That path runs in the background after the save commits. The same page notes that asynchronous paths don't support the ISCHANGED operator, which is another reason to use the Checked Email comparison.

On the asynchronous path

  1. Action: Verify Email. Set the email input to {!$Record.Email}.
  2. Update Records. Update the triggering Lead with:
    • SV_Email_Status__c = Verify Email > 2XX > status
    • SV_Email_Reason__c = Verify Email > 2XX > reason
    • SV_Checked_Email__c = {!$Record.Email}
    • SV_Email_Checked_On__c = {!$Flow.CurrentDateTime}
  3. Fault path on the action: see Step 7.

Leave the immediate path empty, or use it only for same-record field updates that do not need the callout.

Step 7: Handle faults and retries

The considerations page says a response code of 300 or more comes back as an error, handled with a fault path. Connect a fault path from the Verify Email action to an Update Records element that updates the triggering Lead:

  • SV_Email_Status__c = blank
  • SV_Email_Reason__c = {!$Flow.FaultMessage} (it holds the error text)
  • SV_Checked_Email__c = {!$Record.Email}

Setting Checked Email on a fault looks odd, but it matters. This update is itself a record update. If Checked Email stayed blank, the entry formula would still be true, and a persistent error such as a bad key would re-run the flow on every save. A blank status with a filled Checked Email marks "tried, failed" without a loop.

The API's error codes tell you whether to retry:

Code Meaning Action
400 email missing Should not happen with the entry condition; check the input mapping
401 Key missing, wrong or revoked Fix the authentication parameter, then retry
402 Subscription not active Check the plan, then retry
429 Over 120 calls a minute on one key Retry later
503 Service briefly unavailable Retry later

For retries, add a Schedule-Triggered Flow that runs daily on Leads where Email is not blank and Email Status is blank. Its only job is to clear SV_Checked_Email__c. That update makes the entry formula true again, so the record-triggered flow checks the address on its next run. Keep the scheduled flow's filter tight, so a backlog does not fire more calls than the 120-a-minute limit allows. Our guide to handling API rate limits explains what a 429 means and how to space retries.

Step 8: Use the result

The fields are only useful if something reads them:

Status Typical use
valid Eligible for sequences and campaigns
risky Exclude from automated cold sequences; route to the rep; keep for one-to-one email
invalid Exclude from all email sends; flag for research or a phone touch

Build a list view of risky leads grouped by reason. "Accepts all addresses" means a catch-all domain, where no tool can confirm the mailbox. "Role-based address" is a shared inbox. Our guide to accept-all vs unknown results explains how to treat each one.

Worked example: a web-to-lead form

A demo request arrives at 10:02. The illustrative timeline:

  1. Web-to-lead creates a Lead with Email jane@company.com. Checked Email is blank, so the entry formula is true.
  2. The record saves. The asynchronous path starts in the background.
  3. Verify Email calls GET https://simpleverifier.com/api/v1/verify?email=jane@company.com with the Authorization header added by the credential.
  4. The response is status: risky, reason: Accepts all addresses.
  5. The flow writes Email Status risky, Email Reason Accepts all addresses, Checked Email jane@company.com, and the time.
  6. The update does not re-trigger the flow, because Email now equals Checked Email.
  7. The assignment rule sends risky leads to a rep's queue instead of the automated sequence.

If the rep corrects the address to jane.doe@company.com, the formula is true again and the new address is checked.

Bulk loads: keep them out of the flow

A record-triggered flow runs once for each record that meets the conditions. Import 10,000 leads with Data Loader and you queue 10,000 callouts against a key that allows 120 a minute. That is over 80 minutes of calls at best, and a stream of 429 faults at worst.

Two rules keep it safe:

  1. Verify before import. Run the file through a CSV upload first. Import it with Status, Reason and Checked Email already filled. Because Checked Email matches Email, the flow skips those rows.
  2. For existing records, export, verify and update by Id. Our Salesforce export guide and the Salesforce export page cover the steps.

Packs at $9 for 2,500 or $19 for 10,000 emails cover a one-off file. A subscription covers the CSV uploads and the API.

Test before you activate

Use Debug in Flow Builder, then test on real records in a sandbox:

  • A lead with an address you own: expect valid.
  • A made-up address at gmail.com: expect invalid, "Mailbox not found".
  • A lead with no email: the flow should not run.
  • Edit the email on a checked lead: expect a new check.
  • Remove the permission set from a test user and create a lead as that user: expect the fault path to fire. Then add it back.

If every call fails with an authentication error, check the custom header formula, that Generate Authorization Header is cleared, and that the user has the permission set.

The short version

Create an external credential with the key as an authentication parameter and a custom Authorization header that adds Bearer . Point a named credential at https://simpleverifier.com. Create an HTTP Callout action for GET /api/v1/verify with an email query key. In a record-triggered flow, put the callout on the asynchronous path, write status, reason and the checked address back, and use the checked address to stop loops. Add a fault path and a retry. Keep bulk loads out of the flow.

For other CRMs, the same pattern appears in our HubSpot workflow guide. The Pro plan includes API access with no per-call credits; see pricing.

Common questions

Can Salesforce Flow call an external API?

Yes. The HTTP Callout action in Flow Builder connects a flow to an HTTP API without code. You set up an external credential and a named credential in Setup, describe the endpoint and a sample response, and Salesforce generates an invocable action you can use in any flow.

Why does my record-triggered flow fail when it makes a callout?

Salesforce prevents callouts to external services while a record is saving. Salesforce's record-triggered flow considerations say to turn on the asynchronous path under Actions and Related Records and put the callout there. That path runs after the save is committed.

How do I send a Bearer token from a Salesforce HTTP Callout?

Store the key as an authentication parameter on the external credential's permission set mapping. Add a custom header named Authorization whose value is a formula that joins 'Bearer ' with the parameter. Clear Generate Authorization Header on the named credential and allow formulas in the HTTP header.

Will mass-updating records trigger thousands of callouts?

Yes. A record-triggered flow runs for each record that meets its entry conditions. An import of 10,000 leads means 10,000 callouts against an API that allows 120 calls a minute per key. Verify large batches by CSV and write the results back with a data import instead.

Is this different from bounce handling?

Yes. Bounce handling reacts after a real email to the address has bounced. A verification callout checks the mailbox before anything is sent, so a bad address can be kept out of the first send.

Verify unlimited addresses for $29.99/month

Real SMTP mailbox checks. No credits, no per-email fees.

Get Started