Free Email Validity Scoring for D365 Leads (MX Check + Simple Heuristics)
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 =
846280000Risky / needs review =
846280001Likely 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) =10varEmail(String) = Leademailaddress1varDomain(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=0Append
Invalid format;tovarFlagsSkip to the update step
4) Free MX lookup (DNS over HTTPS)
Add an HTTP action:
GET
https://dns.google/resolve?name=@{variables('varDomain')}&type=MXParse 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
846280000Else if score is at least 5, use
846280001Else use
846280002
7) Update the Lead record
Dataverse, Update a row:
kpi_emailvalidityconfidence= mapped value
Optional:
kpi_emailvalidityscore=varScorekpi_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.
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.