All posts

How to Verify Emails in Zoho CRM With a Deluge Function and Workflow Rule

··10 min read

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.

  1. The record is created. The rule's criteria match and the function runs with the lead's ID and email.
  2. clean becomes priya.shah@northwind-example.com.
  3. The record briefly shows check failed / Check did not finish.
  4. The API returns HTTP 200 with status: risky, reason: Accepts all addresses.
  5. The function writes risky, Accepts all addresses and today's date.
  6. 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:

  1. Export Leads with Record Id, Email and any fields you want to keep.
  2. Upload the file to SimpleVerifier. It runs the same syntax, domain, disposable and mailbox checks and returns valid, risky and invalid rows.
  3. 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