How to Verify Emails in Zoho CRM With a Deluge Function and Workflow Rule
To verify emails in Zoho CRM, create a Deluge function that sends the record's email to a verification API with invokeUrl, then writes the status and reason back with zoho.crm.updateRecord. Run it from a workflow rule when a lead or contact is created or its email changes. Keep the API key in a Zoho connection, handle the 30-second limit on automation functions, and verify existing records as a CSV rather than through the workflow.
This guide builds that setup with SimpleVerifier's API. The same structure works with any verification API that accepts an email and returns a status.
What Zoho CRM checks, and what it does not
An email field in Zoho CRM stores an address. It does not tell you whether jane@company.com has a mailbox behind it. That needs a DNS lookup and a short SMTP conversation with the recipient's mail server, which is what a verification API does.
The result is a status you can filter, report on and use in other workflows:
| Status | Meaning | Typical reasons |
|---|---|---|
| valid | The mail server confirmed the mailbox | Mailbox exists |
| risky | It may deliver, but it is not confirmed | Accepts all addresses, Role-based address, Provider hides mailbox status, Server would not confirm |
| invalid | Do not send | Invalid format, No mail server, Disposable address, Mailbox not found |
The API docs list every reason.
Check your edition first
Zoho's platform limits and quotas page says Deluge functions have full access on Enterprise, CRM Plus, Ultimate and Zoho One. A footnote on the same page says: "Standard and Professional editions can only access Functions through Extensions."
If you are on Standard or Professional, skip to the CSV section. You can still verify your records, just not automatically from a workflow rule.
The same page sets the limits that matter here:
| Limit | Value |
|---|---|
| Automation function timeout (Workflow, Blueprint, Approval) | 30 seconds |
| Lines executed per invocation | 200,000 |
| Credits per Deluge execution | 1 |
| Enterprise / Zoho One daily credits | 20,000 + 500 per user license + add-ons |
| Invoke URL calls per day, whole org | 5,000,000 |
The 30-second timeout is the one to plan around. We cover it below.
Step 1: Add the result fields
In the Leads module (and Contacts, if you verify both), add three custom fields:
| Label | Type | Values |
|---|---|---|
| Email Status | Pick list | valid, risky, invalid, check failed |
| Email Reason | Single line | |
| Email Checked On | Date |
Note each field's API name. The code uses API names, not labels. We use Email_Status, Email_Reason and Email_Checked_On below. Change them if Zoho generated something different.
Add check failed to the pick list even though the API never returns it. It marks records where the call did not complete, so you can find and re-check them.
Step 2: Store the API key in a connection
You have two ways to send the key.
Raw header in the script. Put "Authorization": "Bearer sv_live_..." in the headers map. It works, but the key sits in plain text in the function. Anyone who can view or edit the function can copy it.
A connection. Zoho's connections documentation describes custom services with an API Key authentication type. You choose a Param Type of Query string, Form Data or Header, and name the parameter. The key is entered when you connect, not in the script, and you can revoke the connection without editing code.
Use a connection. Create a custom service with:
| Setting | Value |
|---|---|
| Authentication type | API Key |
| Param type | Header |
| Actual parameter | Authorization |
When you connect, enter the full value Bearer sv_live_..., including the word Bearer and the space. Name the connection simpleverifier. The function refers to it by that name.
SimpleVerifier keys start with sv_live_ and are shown once. Create a separate key for Zoho so you can revoke it on its own.
Step 3: Write the function
Create a function in the Automation category with two string arguments, leadId and email. Paste this body:
clean = ifnull(email, "").trim().toLowerCase();
update = Map();
update.put("Email_Checked_On", zoho.currentdate);
if (clean == "" || !clean.contains("@"))
{
update.put("Email_Status", "invalid");
update.put("Email_Reason", "Invalid format");
zoho.crm.updateRecord("Leads", leadId.toLong(), update);
return;
}
// Mark the record first, so a timeout leaves a visible trace
pending = Map();
pending.put("Email_Status", "check failed");
pending.put("Email_Reason", "Check did not finish");
zoho.crm.updateRecord("Leads", leadId.toLong(), pending);
payload = Map();
payload.put("email", clean);
response = invokeurl
[
url: "https://simpleverifier.com/api/v1/verify"
type: POST
body: payload.toString()
headers: {"Content-Type": "application/json"}
connection: "simpleverifier"
detailed: true
];
code = response.get("responseCode");
if (code == 200)
{
result = response.get("responseText").toString().toMap();
update.put("Email_Status", result.get("status"));
update.put("Email_Reason", result.get("reason"));
}
else if (code == 429 || code == 503)
{
update.put("Email_Status", "check failed");
update.put("Email_Reason", "Busy, retry later (HTTP " + code + ")");
}
else
{
update.put("Email_Status", "check failed");
update.put("Email_Reason", "HTTP " + code);
}
resp = zoho.crm.updateRecord("Leads", leadId.toLong(), update);
info resp;
For Contacts, copy the function and change "Leads" to "Contacts".
What each part does
Normalise first. Trimming and lowercasing means the same person typed twice with different capitals is checked once and stored the same way.
POST with a JSON body. Zoho's invokeUrl reference says a key-value body is sent as form data by default, and that the content type in the header must match the body. So the map is converted to JSON text with toString() and sent with Content-Type: application/json. A POST also avoids URL-encoding the address, which matters for addresses with a +.
detailed: true. Per the same reference, this returns the HTTP status code, the response headers and the content as a key-value map, under responseCode, responseHeader and responseText. Without it you get only the body and cannot tell a 429 from a 200.
toString().toMap(). Deluge's toMap function turns JSON text into a key-value map. Converting to text first makes the line work whether the body arrives as text or already parsed.
The early "check failed" write. If the function is cut off, the record still shows that a check started and did not finish. A successful run overwrites it a few seconds later.
No loop when you write back
Zoho's updateRecord reference says that when the options map is not specified, "only approvals, blueprints, and orchestration will get executed by default." Workflow rules do not run. That is why the function can update the same record without triggering itself. Do not add a trigger option that includes workflow.
Step 4: Attach it to a workflow rule
Zoho's workflow rules guide for functions gives the path: Setup > Automation > Workflow Rules, create a rule, then under Instant Actions choose Function and pick yours.
Configure the rule like this:
| Setting | Value |
|---|---|
| Module | Leads |
| When | On a record action: Create or Edit |
| Edit condition | When the Email field is modified, if your Zoho version offers field-specific triggers |
| Criteria | Email is not empty |
| Instant action | Function: your verification function |
Map the arguments to fields. Zoho's guide says to select the CRM field for each argument with a merge variable. Map leadId to the Lead Id and email to the Email field.
Save the rule and check it is active.
The 30-second limit and the 120-a-minute limit
Two limits shape how far you can push this.
Zoho's 30 seconds. Automation functions that exceed 30 seconds are "forcefully terminated", per the limits page. The invokeUrl reference separately says the task throws a socket timeout if the API takes more than 40 seconds, so the 30-second function limit is the one you hit first. Most mailbox checks finish in a few seconds. Some mail servers are slow, and our API docs advise allowing about 30 seconds. When Zoho cuts the function off, the record keeps the "check failed" status from the early write.
The API's 120 calls a minute per key. One workflow execution makes one call, which is fine for leads arriving through forms or created by reps. A mass update or import that fires the rule for thousands of records at once is different. Calls above the limit get HTTP 429, and the function writes "check failed".
Deluge has no simple way to pause and retry inside an automation function, so do not try. Treat "check failed" as a queue. Build a custom view of records where Email Status is check failed or empty, export it, and verify it as a CSV.
Worked example: a web form lead
This example is illustrative. A lead arrives from a Zoho web form with the email Priya.Shah@Northwind-Example.com.
- The record is created. The rule's criteria match and the function runs with the lead's ID and email.
cleanbecomespriya.shah@northwind-example.com.- The record briefly shows
check failed / Check did not finish. - The API returns HTTP 200 with
status: risky,reason: Accepts all addresses. - The function writes
risky,Accepts all addressesand today's date. - A second workflow rule, on Email Status equals
risky, assigns a task to the owner to confirm the address before adding the lead to a cadence.
Credit use: one credit for this execution. On an Enterprise org with 10 users, the daily allowance from Zoho's formula is 20,000 + (10 × 500) = 25,000 credits, shared with your other functions.
What to do with each status
| Status | In Zoho CRM |
|---|---|
| valid | Eligible for cadences and campaigns |
| risky, Accepts all addresses | Catch-all domain. Personal outreach only, or a small test send |
| risky, Role-based address | Shared inbox such as info@. Find a named contact |
| invalid | Exclude from email. Ask the rep to find a working address |
| check failed | Re-verify in the next CSV batch |
Catch-all results are the ones teams argue about. Our explainer on the catch-all domain and the guide to accept-all vs unknown results cover how to treat them.
Existing records: export and verify as a CSV
The workflow only fires on records created or edited after you switch it on. For the leads and contacts you already have, a CSV is faster and cheaper:
- Export Leads with Record Id, Email and any fields you want to keep.
- Upload the file to SimpleVerifier. It runs the same syntax, domain, disposable and mailbox checks and returns valid, risky and invalid rows.
- Import the results back, matching on Record Id, into the Email Status and Email Reason fields.
Our Zoho CRM export guide covers the export step. For the wider cleanup plan, see CRM email cleanup. Re-check on a schedule, because addresses decay. The re-verification cadence guide suggests intervals by list type.
Test before you rely on it
Create five test leads:
- An address you own. Expect valid.
- A made-up address at gmail.com. Expect invalid. Yahoo hides mailbox status, so it is a poor test.
- An address at a domain you know accepts all mail. Expect risky.
- An address with spaces and capitals. Expect a normal result, because the function trims and lowercases it.
- One test with the connection disconnected. Expect "check failed" and an HTTP code in Email Reason.
Read the info resp output in the function's execution log to confirm each update succeeded.
Common problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Email Reason shows HTTP 401 | Connection value missing "Bearer " | Reconnect with Bearer sv_live_... |
| HTTP 402 | Subscription not active | Check the plan; pay-as-you-go packs do not include API access |
| Many "check failed" after an import | Rule fired for every imported record | Verify that batch as a CSV |
| Pick list not updated | Value not in the pick list, or wrong API name | Add the value; use the field's API name |
| Function not available | Standard or Professional edition | Use the CSV route |
What it costs
On SimpleVerifier, API access comes with Pro at $29.99 a month, with unlimited email verification and no per-call credits. Email + Phone at $49.99 a month adds 5,000 live phone carrier lookups through the same API. Pay-as-you-go packs, $9 for 2,500 or $19 for 10,000 emails, cover CSV uploads only, which suits a one-off cleanup of existing records. If you want every new Zoho lead checked automatically, see pricing.
The short version
Add Email Status, Email Reason and Email Checked On fields. Store the key in an API Key connection that sends an Authorization: Bearer header. In a Deluge function, normalise the email, mark the record "check failed", POST to the verification endpoint with detailed: true, and write the status and reason back with zoho.crm.updateRecord. Run it from a workflow rule on create and email edits. Re-verify "check failed" and existing records as a CSV.
Common questions
Does Zoho CRM verify email addresses?
Not the mailbox. An email field stores an address, but nothing in it confirms that the mailbox exists. For that, call an email verification API from a Deluge function and store the result in a custom field.
Which Zoho CRM editions can run this?
Zoho's platform limits page says functions have full access on Enterprise, CRM Plus, Ultimate and Zoho One, and that Standard and Professional can only use functions through extensions. On those editions, verify by exporting records to CSV instead.
Why did my workflow function stop without updating the record?
Zoho terminates automation functions that run longer than 30 seconds. A slow recipient mail server can push a verification close to that. Write a fallback status before the call, or treat empty statuses as 'not checked' and re-verify them in bulk.
Will updating the record from the function trigger the workflow again?
Not by default. Zoho's documentation for zoho.crm.updateRecord says that when the options map is not given, only approvals, blueprints and orchestration run, not workflow rules. Leave the trigger option out and there is no loop.
Should I verify all my existing Zoho CRM leads with this function?
No. Each execution uses a function credit and one API call, and the verification API allows 120 calls a minute per key. Export existing leads, verify the CSV, and import the results by record ID. Keep the function for new and changed records.
Verify unlimited addresses for $29.99/month
Real SMTP mailbox checks. No credits, no per-email fees.
Get Started