Key Takeaways
- The idiomatic ASP.NET Core pattern is a typed HttpClient registered through dependency injection, never a raw new HttpClient() per call.
- System.Text.Json with a strongly typed result record deserializes the v2 response cleanly without third-party JSON libraries.
- Polly, integrated through AddPolicyHandler, adds retry with exponential backoff and a circuit breaker at the HTTP layer with no custom retry code.
- A 16-stage verification on the v2 endpoint surfaces mailbox existence plus disposable, role-account, and gibberish flags, gating signups before bad data reaches the database.
C# and ASP.NET Core have the cleanest dependency injection story of any major web framework, and email verification is a textbook case for it. Rather than newing up an HttpClient in a controller, you register a typed client in the service container, attach a Polly retry policy at registration, and inject the verifier wherever it is needed. This guide builds that pattern end to end against the v2 API, on .NET 8 and C# 12.
The complete reference implementation, including framework-specific notes for Minimal APIs and MVC, is documented on the verify email with C# integration page.
The v2 Endpoint
Confirm the endpoint against a known address before writing C#. The v2 verify endpoint takes the email as a query parameter and returns a JSON document with all the decision fields.
The status field drives the decision. Passed means the mailbox exists and accepts mail. Failed means the mailbox does not exist or the domain has no MX server. Unknown covers greylisting and transient errors. The boolean flags surface risk categories worth handling separately.
The Result Record and Typed Client
Model the response as a record with JsonPropertyName attributes so System.Text.Json maps the snake_case API fields to C# properties. The client itself is a class that takes an injected HttpClient, which is what makes it a typed client in ASP.NET Core terms.
The verifier takes HttpClient through its constructor. It does not create the client, configure the base address, or manage its lifetime. That is all handled at registration, which is the pattern that avoids socket exhaustion from new HttpClient() and gives Polly a place to attach.
Registration With Polly Retry
Register the typed client in Program.cs. Set the base address and timeout once, and attach a Polly retry policy that handles transient HTTP failures with exponential backoff. This is where the resilience lives.
The retry policy handles 5xx responses and network failures with three attempts at exponentially increasing delays. Because it is attached at the HttpClient layer, every call through the typed client gets the resilience automatically, and the verifier class stays clean of retry logic.
The Controller
The controller injects IEmailVerifier and gates the signup. A failed status or a disposable flag returns 422 before the user record is ever created.
The CancellationToken flows from the controller through the verifier to the HttpClient, so a cancelled request aborts the in-flight verification cleanly. This is the idiomatic async pattern in ASP.NET Core and matters under load.
Notice how the controller treats the three failure categories distinctly. A null result means the verification service itself was unreachable after retries, which is an infrastructure problem and returns 503 so the client can retry later. A failed status means the address is genuinely bad and returns 422 with the specific sub_status so the front end can show a useful message. A disposable flag is a policy rejection rather than a deliverability problem, so it returns its own error code. Collapsing these into a single generic rejection loses the signal that downstream systems and front-end UX both need.
For a typed client, the verification metadata should be persisted alongside the user record rather than discarded after the gate decision. Storing the status, sub_status, and risk flags lets later processes (lead scoring, fraud review, deliverability segmentation) read the verification result without re-calling the API. The verification you already paid for becomes a reusable data point rather than a one-time gate check.
For broader integration patterns across other languages, the email verification integrations hub covers Node.js, Python, Go, Java, PHP, Ruby, and the rest. New developers can grab 100 free email verification credits on signup to test the integration, and the email verification API documentation covers the full response schema.
Frequently Asked Questions
Why use a typed HttpClient instead of IHttpClientFactory directly?
The typed client gives you a strongly typed service to inject, encapsulates the API logic in one class, and still gets all the factory benefits: managed lifetime, connection pooling, and a place to attach Polly policies. It is the recommended pattern in the ASP.NET Core documentation.
Where should the API key be stored?
In configuration, loaded from User Secrets in development and environment variables or a key vault in production. Read it through IConfiguration as shown. Never hardcode the key or commit it to source control.
Does this work with Minimal APIs?
Yes. Register the typed client the same way in Program.cs, then inject IEmailVerifier directly into the endpoint delegate. The verifier and result record are identical; only the endpoint definition changes from a controller to a MapPost call.
How do I handle the verification service being down?
The controller returns 503 when the result is null after retries are exhausted. For a fail-soft approach, allow the signup with an unverified flag and re-verify asynchronously through a background hosted service once the API recovers.