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 =
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) =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=0 - Append
Invalid format;tovarFlags - 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=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.