Alos.Email.Validation
1.4.0
dotnet add package Alos.Email.Validation --version 1.4.0
NuGet\Install-Package Alos.Email.Validation -Version 1.4.0
<PackageReference Include="Alos.Email.Validation" Version="1.4.0" />
<PackageVersion Include="Alos.Email.Validation" Version="1.4.0" />
<PackageReference Include="Alos.Email.Validation" />
paket add Alos.Email.Validation --version 1.4.0
#r "nuget: Alos.Email.Validation, 1.4.0"
#:package Alos.Email.Validation@1.4.0
#addin nuget:?package=Alos.Email.Validation&version=1.4.0
#tool nuget:?package=Alos.Email.Validation&version=1.4.0
Alos.Email.Validation
Email validation library for anti-abuse protection in SaaS applications.
Features
- Format Validation - HTML5 Living Standard compliant email format validation
- Provider-Specific Normalization - Gmail dots/plus, Outlook plus, ProtonMail plus, etc.
- Relay Service Blocking - Apple Hide My Email, Firefox Relay, DuckDuckGo, SimpleLogin, etc.
- Disposable Domain Detection - The community-maintained disposable-email-domains list (38,000+ domains), which the built-in auto-updater downloads and keeps current or which a blocklist directory supplies; the package embeds a starter list that is always loaded
- MX Record Verification - DNS-based domain validation with fail-open strategy
- MX Whitelist - Bypass MX validation for test/development domains
- Custom Blocklist/Allowlist - Override built-in lists with your own domains
- Hot-Reload - Runtime blocklist updates without redeployment
- Auto-Update - Background service for periodic blocklist updates from GitHub
- Thread-Safe - All operations are safe for concurrent access
Installation
dotnet add package Alos.Email.Validation
Quick Start
// Register services
builder.Services.AddEmailValidation(builder.Configuration);
// Or with auto-update
builder.Services.AddEmailValidationWithAutoUpdate(builder.Configuration);
{
"EmailValidation": {
"BlocklistDirectory": "/var/lib/alos/email-blocklists",
"EnableAutoUpdate": true,
"UpdateInterval": "1.00:00:00",
"InitialUpdateDelay": "00:00:30",
"CustomBlocklist": ["spammer.com"],
"CustomAllowlist": ["legitimate-service.com"],
"CustomBlocklistUrls": ["https://example.com/my-blocklist.txt"],
"CustomAllowlistUrls": ["https://example.com/my-allowlist.txt"],
"WhitelistedMxDomains": ["test.local", "itest.alos.local"]
}
}
API Reference
IEmailValidationService
| Method | Description |
|---|---|
ValidateEmailAsync(email, ct) |
Full async validation: format + relay + disposable + MX |
ValidateMxAsync(email, ct) |
MX record validation only (use when other checks done separately) |
ValidateFormat(email) |
Synchronous HTML5 format validation |
IsRelayService(email) |
Check if email uses a relay/forwarding service |
IsDisposable(email) |
Check if email domain is disposable |
Usage Patterns
Full Validation (All-in-One)
public class RegistrationService(IEmailValidationService emailValidator)
{
public async Task<bool> RegisterAsync(string email, CancellationToken ct)
{
// Full async validation (format + relay + disposable + MX)
var result = await emailValidator.ValidateEmailAsync(email, ct);
if (!result.IsValid)
{
// result.Error: InvalidFormat, RelayService, Disposable, InvalidDomain
return false;
}
// Get normalized email for uniqueness checks
var normalized = EmailNormalizer.Normalize(email);
// Continue with registration...
return true;
}
}
With FluentValidation (Split Validation)
When using FluentValidation for synchronous checks, use ValidateMxAsync in your handler
to avoid duplicating validation:
// Validator (synchronous checks)
public class RegisterCommandValidator : AbstractValidator<RegisterCommand>
{
public RegisterCommandValidator(IEmailValidationService emailValidator)
{
RuleFor(x => x.Email)
.NotEmpty()
.Must(emailValidator.ValidateFormat).WithMessage("Invalid email format")
.Must(e => !emailValidator.IsRelayService(e)).WithMessage("Relay services not allowed")
.Must(e => !emailValidator.IsDisposable(e)).WithMessage("Disposable emails not allowed");
}
}
// Handler (async MX check only)
public class RegisterCommandHandler(IEmailValidationService emailValidator)
{
public async Task<Result> Handle(RegisterCommand cmd, CancellationToken ct)
{
// MX validation only (format/relay/disposable already validated)
var mxResult = await emailValidator.ValidateMxAsync(cmd.Email, ct);
if (!mxResult.IsValid)
return Result.Failure(Errors.InvalidDomain);
// Continue with registration...
}
}
Individual Checks
// Format validation (synchronous)
if (!emailValidator.ValidateFormat(email))
return Error("Invalid email format");
// Relay service check
if (emailValidator.IsRelayService(email))
return Error("Relay services not allowed");
// Disposable domain check
if (emailValidator.IsDisposable(email))
return Error("Disposable emails not allowed");
// Normalization for uniqueness
var normalized = EmailNormalizer.Normalize(email);
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
BlocklistDirectory |
string | null | Directory for blocklist files, merged into the embedded lists. When null with auto-update on: LocalApplicationData/alos/email-blocklists |
EnableAutoUpdate |
bool | false | Enable background blocklist updates; takes effect with the AddEmailValidationWithAutoUpdate registration |
UpdateInterval |
TimeSpan | 1 day | Interval between blocklist updates |
InitialUpdateDelay |
TimeSpan | 30s | Delay before first update |
MinimumBlocklistEntryCount |
int | 0 | Floor for the primary blocklist (0: no check): a shorter download is not written, and the host start aborts when the loaded blocklist holds fewer entries |
CustomBlocklist |
string[] | [] | Additional domains to block |
CustomAllowlist |
string[] | [] | Domains to exclude from blocking |
CustomBlocklistUrls |
string[] | [] | URLs to fetch blocklists from |
CustomAllowlistUrls |
string[] | [] | URLs to fetch allowlists from |
WhitelistedMxDomains |
string[] | [] | Domains that bypass MX validation |
A host that sets MinimumBlocklistEntryCount mounts or seeds a blocklist directory holding the full list, because the floor is compared at start, before the first update cycle.
WhitelistedMxDomains
Use this option to whitelist domains that should bypass MX validation (e.g., test domains without real DNS records):
{
"EmailValidation": {
"WhitelistedMxDomains": ["test.local", "itest.alos.local", "example.com"]
}
}
Whitelisted domains:
- Return success from
ValidateMxAsyncwithout DNS lookup - Return success from
ValidateEmailAsyncfor MX check (other checks still apply) - Are matched case-insensitively
Components
| Component | Description |
|---|---|
IEmailValidationService |
Orchestrates all validation checks |
IDisposableEmailDomainChecker |
Disposable domain detection with hot-reload |
IMxRecordValidator |
DNS MX record verification |
EmailNormalizer |
Static class for provider-specific email normalization |
RelayServiceBlocklist |
Static class blocking 21+ relay/forwarding services |
Version 1.4.0 adds the BlockedDomainCount property (the loaded blocklist's entry count) to IDisposableEmailDomainChecker; a consumer that implements the interface must add it.
Framework Support
- .NET 8.0
- .NET 9.0
- .NET 10.0
License
Apache-2.0 License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- DnsClient (>= 1.8.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Http (>= 8.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Options (>= 8.0.0)
-
net8.0
- DnsClient (>= 1.8.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Http (>= 8.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Options (>= 8.0.0)
-
net9.0
- DnsClient (>= 1.8.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Http (>= 8.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Options (>= 8.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.