Free Email Validity Scoring for D365 Leads (MX Check + Simple Heuristics)

Jason Bell· Founder, Bells & Pixels January 16, 2026 5 min read

Free Email Validity Scoring for D365 Leads (MX Check + Simple Heuristics)

Inbound leads are messy. Before a rep wastes time, you want a fast signal: is this email probably real, and is it likely tied to an actual organization?

This guide shows how to build a 100% free email validity confidence check for Dynamics 365 / Dataverse Leads using Power Automate. It:

  • Checks email format
  • Verifies the domain can receive email (MX record lookup)
  • Flags disposable domains, free email domains, and role-based inboxes
  • Produces a 0 to 10 confidence score
  • Updates a Lead choice field:
    • 8 to 10: Likely valid
    • 5 to 7: Risky / needs review
    • 0 to 4: Likely invalid or low quality

What this does (and doesn't do)

This approach is designed for triage, not perfect deliverability verification.

Great for catching: fake domains, typo domains, disposable emails, low-quality submissions.

Not guaranteed to detect: catch-all inboxes, spam traps, "mailbox exists" certainty.

If you need mailbox-level certainty, you'll eventually pair this with a paid validation API. But for quick qualification, this free method delivers a ton of value.

Prerequisites (Dataverse)

On the Lead table, create a Choice field:

kpi_emailvalidityconfidence (Display name: Email Validity Confidence)

Options and values (your current values):

  • Likely valid = 846280000
  • Risky / needs review = 846280001
  • Likely invalid or low quality = 846280002

Recommended but optional for explainability:

  • kpi_emailvalidityscore (Whole number)
  • kpi_emailvalidityflags (Single line or multiple lines of text)

Scoring model (0 to 10)

Start at 10, then subtract:

  • 6 if domain has no MX record
  • 3 if domain is disposable
  • 2 if domain is a free email provider
  • 1 if local part is role-based (info@, sales@, etc.)
  • Score = 0 if email fails basic format checks

Then map to confidence:

  • 8 to 10 produces Likely valid
  • 5 to 7 produces Risky / needs review
  • 0 to 4 produces Likely invalid or low quality

Build the flow: "Lead - Email Validity Confidence (Free DNS)"

1) Trigger

Dataverse, When a row is added, modified or deleted

  • Change type: Added
  • Table: Leads
  • Scope: Organization

Optional: filter to only CI form leads (if you have a "lead source" indicator).

2) Initialize variables

Create these variables:

  • varScore (Integer) = 10
  • varEmail (String) = Lead emailaddress1
  • varDomain (String) = toLower(last(split(variables('varEmail'), '@')))
  • varLocal (String) = toLower(first(split(variables('varEmail'), '@')))
  • varFlags (String) = empty

3) Basic format gate

Condition expression:

and(
  contains(variables('varEmail'), '@'),
  contains(variables('varDomain'), '.'),
  greater(length(variables('varLocal')), 0),
  greater(length(variables('varDomain')), 3)
)

If No:

  • Set varScore = 0
  • Append Invalid format; to varFlags
  • Skip to the update step

4) Free MX lookup (DNS over HTTPS)

Add an HTTP action:

GET

https://dns.google/resolve?name=@{variables('varDomain')}&type=MX

Parse the JSON response (Parse JSON action) using this schema:

{
"type":"object",
"properties":{
"Status":{"type":"integer"},
"Answer":{
"type":["array","null"],
"items":{
"type":"object",
"properties":{
"name":{"type":"string"},
"type":{"type":"integer"},
"TTL":{"type":"integer"},
"data":{"type":"string"}
},
"required":["name","type","TTL","data"]
}
}
}
}

Condition expression to check MX:

and(
  equals(body('Parse_JSON')?['Status'], 0),
  greater(length(coalesce(body('Parse_JSON')?['Answer'], createArray())), 0)
)

If No:

  • varScore = max(0, sub(varScore, 6))
  • Append No MX;

5) Disposable / free / role-based checks (free lists)

Add 3 Array variables.

varDisposableDomains:

[
"mailinator.com",
"guerrillamail.com",
"10minutemail.com",
"tempmail.com",
"yopmail.com",
"trashmail.com",
"getnada.com",
"dispostable.com"
]

varFreeDomains:

[
"gmail.com",
"yahoo.com",
"outlook.com",
"hotmail.com",
"icloud.com",
"aol.com",
"proton.me",
"protonmail.com",
"live.com"
]

varRoleLocals:

[
"info",
"sales",
"support",
"help",
"admin",
"contact",
"billing",
"accounts",
"hello",
"inquiries"
]

Then conditions:

Disposable?

contains(variables('varDisposableDomains'), variables('varDomain'))

If Yes: -3, flag.

Free domain?

contains(variables('varFreeDomains'), variables('varDomain'))

If Yes: -2, flag.

Role-based local?

contains(variables('varRoleLocals'), variables('varLocal'))

If Yes: -1, flag.

6) Map score to Choice field (your exact values)

  • If score is at least 8, use 846280000
  • Else if score is at least 5, use 846280001
  • Else use 846280002

7) Update the Lead record

Dataverse, Update a row:

  • kpi_emailvalidityconfidence = mapped value

Optional:

  • kpi_emailvalidityscore = varScore
  • kpi_emailvalidityflags = varFlags

Want the whole argument, sourced?

The Owned-Media Pivot white paper makes this case in full for marketing and revenue leaders, with the evidence dated and cited. Get the PDF by email.

Power AutomateDynamics 365DataverseLead QualificationEmail ValidationDNSPower Platform

Jason Bell

Founder, Bells & Pixels, Bells & Pixels

Bells & Pixels is a small studio that builds the publishing engine behind this series, Toudai, and runs on it as customer zero.